[MERGE] osv_memory removed, merged into database-backed Model/TransientModel
bzr revid: odo@openerp.com-20110927213712-9aa0x0aqoh13f6ga
This commit is contained in:
commit
762637518b
|
@ -22,6 +22,8 @@
|
||||||
""" OpenERP core library.
|
""" OpenERP core library.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# The hard-coded super-user id (a.k.a. administrator, or root user).
|
||||||
|
SUPERUSER_ID = 1
|
||||||
|
|
||||||
import addons
|
import addons
|
||||||
import conf
|
import conf
|
||||||
|
|
|
@ -63,7 +63,7 @@ class ir_model(osv.osv):
|
||||||
models = self.browse(cr, uid, ids, context=context)
|
models = self.browse(cr, uid, ids, context=context)
|
||||||
res = dict.fromkeys(ids)
|
res = dict.fromkeys(ids)
|
||||||
for model in models:
|
for model in models:
|
||||||
res[model.id] = isinstance(self.pool.get(model.model), osv.osv_memory)
|
res[model.id] = self.pool.get(model.model).is_transient()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
|
def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
|
||||||
|
@ -165,7 +165,7 @@ class ir_model(osv.osv):
|
||||||
pass
|
pass
|
||||||
x_custom_model._name = model
|
x_custom_model._name = model
|
||||||
x_custom_model._module = False
|
x_custom_model._module = False
|
||||||
a = x_custom_model.createInstance(self.pool, cr)
|
a = x_custom_model.create_instance(self.pool, cr)
|
||||||
if (not a._columns) or ('x_name' in a._columns.keys()):
|
if (not a._columns) or ('x_name' in a._columns.keys()):
|
||||||
x_name = 'x_name'
|
x_name = 'x_name'
|
||||||
else:
|
else:
|
||||||
|
@ -481,14 +481,12 @@ class ir_model_access(osv.osv):
|
||||||
|
|
||||||
if isinstance(model, browse_record):
|
if isinstance(model, browse_record):
|
||||||
assert model._table_name == 'ir.model', 'Invalid model object'
|
assert model._table_name == 'ir.model', 'Invalid model object'
|
||||||
model_name = model.name
|
model_name = model.model
|
||||||
else:
|
else:
|
||||||
model_name = model
|
model_name = model
|
||||||
|
|
||||||
# osv_memory objects can be read by everyone, as they only return
|
# TransientModel records have no access rights, only an implicit access rule
|
||||||
# results that belong to the current user (except for superuser)
|
if self.pool.get(model_name).is_transient():
|
||||||
model_obj = self.pool.get(model_name)
|
|
||||||
if isinstance(model_obj, osv.osv_memory):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# We check if a specific rule exists
|
# We check if a specific rule exists
|
||||||
|
@ -523,7 +521,7 @@ class ir_model_access(osv.osv):
|
||||||
}
|
}
|
||||||
|
|
||||||
raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
|
raise except_orm(_('AccessError'), msgs[mode] % (model_name, groups) )
|
||||||
return r
|
return r or False
|
||||||
|
|
||||||
__cache_clearing_methods = []
|
__cache_clearing_methods = []
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,7 @@ from functools import partial
|
||||||
import tools
|
import tools
|
||||||
from tools.safe_eval import safe_eval as eval
|
from tools.safe_eval import safe_eval as eval
|
||||||
from tools.misc import unquote as unquote
|
from tools.misc import unquote as unquote
|
||||||
|
from openerp import SUPERUSER_ID
|
||||||
SUPERUSER_UID = 1
|
|
||||||
|
|
||||||
class ir_rule(osv.osv):
|
class ir_rule(osv.osv):
|
||||||
_name = 'ir.rule'
|
_name = 'ir.rule'
|
||||||
|
@ -68,7 +67,7 @@ class ir_rule(osv.osv):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _check_model_obj(self, cr, uid, ids, context=None):
|
def _check_model_obj(self, cr, uid, ids, context=None):
|
||||||
return not any(isinstance(self.pool.get(rule.model_id.model), osv.osv_memory) for rule in self.browse(cr, uid, ids, context))
|
return not any(self.pool.get(rule.model_id.model).is_transient() for rule in self.browse(cr, uid, ids, context))
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.char('Name', size=128, select=1),
|
'name': fields.char('Name', size=128, select=1),
|
||||||
|
@ -104,7 +103,7 @@ class ir_rule(osv.osv):
|
||||||
if mode not in self._MODES:
|
if mode not in self._MODES:
|
||||||
raise ValueError('Invalid mode: %r' % (mode,))
|
raise ValueError('Invalid mode: %r' % (mode,))
|
||||||
|
|
||||||
if uid == SUPERUSER_UID:
|
if uid == SUPERUSER_ID:
|
||||||
return None
|
return None
|
||||||
cr.execute("""SELECT r.id
|
cr.execute("""SELECT r.id
|
||||||
FROM ir_rule r
|
FROM ir_rule r
|
||||||
|
@ -117,10 +116,10 @@ class ir_rule(osv.osv):
|
||||||
rule_ids = [x[0] for x in cr.fetchall()]
|
rule_ids = [x[0] for x in cr.fetchall()]
|
||||||
if rule_ids:
|
if rule_ids:
|
||||||
# browse user as super-admin root to avoid access errors!
|
# browse user as super-admin root to avoid access errors!
|
||||||
user = self.pool.get('res.users').browse(cr, SUPERUSER_UID, uid)
|
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid)
|
||||||
global_domains = [] # list of domains
|
global_domains = [] # list of domains
|
||||||
group_domains = {} # map: group -> list of domains
|
group_domains = {} # map: group -> list of domains
|
||||||
for rule in self.browse(cr, SUPERUSER_UID, rule_ids):
|
for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
|
||||||
# read 'domain' as UID to have the correct eval context for the rule.
|
# read 'domain' as UID to have the correct eval context for the rule.
|
||||||
rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
|
rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
|
||||||
dom = expression.normalize(rule_domain)
|
dom = expression.normalize(rule_domain)
|
||||||
|
|
|
@ -19,18 +19,15 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from osv import osv
|
import openerp
|
||||||
from osv.orm import orm_memory
|
|
||||||
|
|
||||||
class osv_memory_autovacuum(osv.osv_memory):
|
class osv_memory_autovacuum(openerp.osv.osv.osv_memory):
|
||||||
|
""" Expose the osv_memory.vacuum() method to the cron jobs mechanism. """
|
||||||
_name = 'osv_memory.autovacuum'
|
_name = 'osv_memory.autovacuum'
|
||||||
|
|
||||||
def power_on(self, cr, uid, context=None):
|
def power_on(self, cr, uid, context=None):
|
||||||
for model in self.pool.obj_list():
|
for model in self.pool.models.values():
|
||||||
obj = self.pool.get(model)
|
if model.is_transient():
|
||||||
if isinstance(obj, orm_memory):
|
model._transient_vacuum(cr, uid)
|
||||||
obj.vaccum(cr, uid)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
osv_memory_autovacuum()
|
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ class res_currency(osv.osv):
|
||||||
(name, (COALESCE(company_id,-1)))""")
|
(name, (COALESCE(company_id,-1)))""")
|
||||||
|
|
||||||
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
|
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
|
||||||
res = super(osv.osv, self).read(cr, user, ids, fields, context, load)
|
res = super(res_currency, self).read(cr, user, ids, fields, context, load)
|
||||||
currency_rate_obj = self.pool.get('res.currency.rate')
|
currency_rate_obj = self.pool.get('res.currency.rate')
|
||||||
for r in res:
|
for r in res:
|
||||||
if r.__contains__('rate_ids'):
|
if r.__contains__('rate_ids'):
|
||||||
|
|
|
@ -144,45 +144,3 @@
|
||||||
!python {model: res.partner.category}: |
|
!python {model: res.partner.category}: |
|
||||||
self.pool._init = True
|
self.pool._init = True
|
||||||
|
|
||||||
-
|
|
||||||
"OSV Memory: Verify that osv_memory properly handles large data allocation"
|
|
||||||
-
|
|
||||||
1. No "count-based" auto-vaccuum when max_count is disabled
|
|
||||||
-
|
|
||||||
!python {model: base.language.export}: |
|
|
||||||
# setup special limits for the test, these will be reset at next pool reload anyway
|
|
||||||
self._max_count = None
|
|
||||||
num_recs = 250
|
|
||||||
for i in xrange(num_recs):
|
|
||||||
self.create(cr, uid, {'format':'po'})
|
|
||||||
assert (len(self.datas) >= num_recs), "OSV Memory must not auto-vaccum records from the current transaction if max_count is not set"
|
|
||||||
-
|
|
||||||
2. Auto-vaccuum should be enabled when max_count is set
|
|
||||||
-
|
|
||||||
!python {model: base.language.export}: |
|
|
||||||
# setup special limits for the test, these will be reset at next pool reload anyway
|
|
||||||
self._max_count = 100
|
|
||||||
num_recs = 219
|
|
||||||
for i in xrange(num_recs):
|
|
||||||
self.create(cr, uid, {'name': i, 'format':'po'})
|
|
||||||
assert (self._max_count <= len(self.datas) < self._max_count + self._check_time), "OSV Memory must auto-expire records when max_count is reached"
|
|
||||||
for k,v in self.datas.iteritems():
|
|
||||||
assert (int(v['name']) >= (num_recs - (self._max_count + self._check_time))), "OSV Memory must auto-expire records based on age"
|
|
||||||
-
|
|
||||||
3. Auto-vaccuum should be based on age only when max_count is not set
|
|
||||||
-
|
|
||||||
!python {model: base.language.export}: |
|
|
||||||
# setup special limits for the test, these will be reset at next pool reload anyway
|
|
||||||
self._max_count = None
|
|
||||||
self._max_hours = 0.01 #36 seconds
|
|
||||||
num_recs = 200
|
|
||||||
for i in xrange(num_recs):
|
|
||||||
self.create(cr, uid, {'format':'po'})
|
|
||||||
assert (len(self.datas) >= num_recs), "OSV Memory must not auto-expire records from the current transaction"
|
|
||||||
|
|
||||||
# expire all records
|
|
||||||
for k,v in self.datas.iteritems():
|
|
||||||
v['internal.date_access'] = 0
|
|
||||||
self.vaccum(cr, 1, force=True)
|
|
||||||
|
|
||||||
assert (len(self.datas) == 0), "OSV Memory must expire old records after vaccuum"
|
|
||||||
|
|
|
@ -339,16 +339,16 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||||
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
|
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
|
||||||
for (model, name) in cr.fetchall():
|
for (model, name) in cr.fetchall():
|
||||||
model_obj = pool.get(model)
|
model_obj = pool.get(model)
|
||||||
if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
|
if model_obj and not model_obj.is_transient():
|
||||||
logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
|
logger.notifyChannel('init', netsvc.LOG_WARNING, 'Model %s (%s) has no access rules!' % (model, name))
|
||||||
|
|
||||||
# Temporary warning while we remove access rights on osv_memory objects, as they have
|
# Temporary warning while we remove access rights on osv_memory objects, as they have
|
||||||
# been replaced by owner-only access rights
|
# been replaced by owner-only access rights
|
||||||
cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
|
cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
|
||||||
for (model, name) in cr.fetchall():
|
for (model, name) in cr.fetchall():
|
||||||
model_obj = pool.get(model)
|
model_obj = pool.get(model)
|
||||||
if isinstance(model_obj, osv.osv.osv_memory) and not isinstance(model_obj, osv.osv.osv):
|
if model_obj and model_obj.is_transient():
|
||||||
logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
|
logger.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model %s (%s) should not have explicit access rules!' % (model, name))
|
||||||
|
|
||||||
cr.execute("SELECT model from ir_model")
|
cr.execute("SELECT model from ir_model")
|
||||||
for (model,) in cr.fetchall():
|
for (model,) in cr.fetchall():
|
||||||
|
@ -356,7 +356,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||||
if obj:
|
if obj:
|
||||||
obj._check_removed_columns(cr, log=True)
|
obj._check_removed_columns(cr, log=True)
|
||||||
else:
|
else:
|
||||||
logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
|
logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)" % model)
|
||||||
|
|
||||||
# Cleanup orphan records
|
# Cleanup orphan records
|
||||||
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
|
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
|
||||||
|
|
|
@ -277,7 +277,6 @@ def init_module_models(cr, module_name, obj_list):
|
||||||
TODO better explanation of _auto_init and init.
|
TODO better explanation of _auto_init and init.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.notifyChannel('init', netsvc.LOG_INFO,
|
logger.notifyChannel('init', netsvc.LOG_INFO,
|
||||||
'module %s: creating or updating database tables' % module_name)
|
'module %s: creating or updating database tables' % module_name)
|
||||||
todo = []
|
todo = []
|
||||||
|
|
|
@ -89,10 +89,10 @@ class Registry(object):
|
||||||
|
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
# Instantiate registered classes (via metamodel discovery or via explicit
|
# Instantiate registered classes (via the MetaModel automatic discovery
|
||||||
# constructor call), and add them to the pool.
|
# or via explicit constructor call), and add them to the pool.
|
||||||
for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
|
for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
|
||||||
res.append(cls.createInstance(self, cr))
|
res.append(cls.create_instance(self, cr))
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
|
@ -450,7 +450,7 @@ class expression(object):
|
||||||
# field could not be found in model columns, it's probably invalid, unless
|
# field could not be found in model columns, it's probably invalid, unless
|
||||||
# it's one of the _log_access special fields
|
# it's one of the _log_access special fields
|
||||||
# TODO: make these fields explicitly available in self.columns instead!
|
# TODO: make these fields explicitly available in self.columns instead!
|
||||||
if (field_path[0] not in MAGIC_COLUMNS) and (left not in MAGIC_COLUMNS):
|
if field_path[0] not in MAGIC_COLUMNS:
|
||||||
raise ValueError("Invalid field %r in domain expression %r" % (left, exp))
|
raise ValueError("Invalid field %r in domain expression %r" % (left, exp))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ class _column(object):
|
||||||
_symbol_set = (_symbol_c, _symbol_f)
|
_symbol_set = (_symbol_c, _symbol_f)
|
||||||
_symbol_get = None
|
_symbol_get = None
|
||||||
|
|
||||||
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete="set null", translate=False, select=False, manual=False, **args):
|
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
The 'manual' keyword argument specifies if the field is a custom one.
|
The 'manual' keyword argument specifies if the field is a custom one.
|
||||||
|
@ -91,7 +91,7 @@ class _column(object):
|
||||||
self.help = args.get('help', '')
|
self.help = args.get('help', '')
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
self.change_default = change_default
|
self.change_default = change_default
|
||||||
self.ondelete = ondelete
|
self.ondelete = ondelete.lower() if ondelete else None # defaults to 'set null' in ORM
|
||||||
self.translate = translate
|
self.translate = translate
|
||||||
self._domain = domain
|
self._domain = domain
|
||||||
self._context = context
|
self._context = context
|
||||||
|
@ -112,12 +112,6 @@ class _column(object):
|
||||||
def set(self, cr, obj, id, name, value, user=None, context=None):
|
def set(self, cr, obj, id, name, value, user=None, context=None):
|
||||||
cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
|
cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
|
||||||
|
|
||||||
def set_memory(self, cr, obj, id, name, value, user=None, context=None):
|
|
||||||
raise Exception(_('Not implemented set_memory method !'))
|
|
||||||
|
|
||||||
def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
|
|
||||||
raise Exception(_('Not implemented get_memory method !'))
|
|
||||||
|
|
||||||
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
||||||
raise Exception(_('undefined get method !'))
|
raise Exception(_('undefined get method !'))
|
||||||
|
|
||||||
|
@ -126,9 +120,6 @@ class _column(object):
|
||||||
res = obj.read(cr, uid, ids, [name], context=context)
|
res = obj.read(cr, uid, ids, [name], context=context)
|
||||||
return [x[name] for x in res]
|
return [x[name] for x in res]
|
||||||
|
|
||||||
def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
|
|
||||||
raise Exception(_('Not implemented search_memory method !'))
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Simple fields
|
# Simple fields
|
||||||
|
@ -269,7 +260,7 @@ class binary(_column):
|
||||||
_column.__init__(self, string=string, **args)
|
_column.__init__(self, string=string, **args)
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
|
|
||||||
def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
|
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
if not values:
|
if not values:
|
||||||
|
@ -293,9 +284,6 @@ class binary(_column):
|
||||||
res[i] = val
|
res[i] = val
|
||||||
return res
|
return res
|
||||||
|
|
||||||
get = get_memory
|
|
||||||
|
|
||||||
|
|
||||||
class selection(_column):
|
class selection(_column):
|
||||||
_type = 'selection'
|
_type = 'selection'
|
||||||
|
|
||||||
|
@ -355,30 +343,6 @@ class many2one(_column):
|
||||||
_column.__init__(self, string=string, **args)
|
_column.__init__(self, string=string, **args)
|
||||||
self._obj = obj
|
self._obj = obj
|
||||||
|
|
||||||
def set_memory(self, cr, obj, id, field, values, user=None, context=None):
|
|
||||||
obj.datas.setdefault(id, {})
|
|
||||||
obj.datas[id][field] = values
|
|
||||||
|
|
||||||
def get_memory(self, cr, obj, ids, name, user=None, context=None, values=None):
|
|
||||||
result = {}
|
|
||||||
for id in ids:
|
|
||||||
result[id] = obj.datas[id].get(name, False)
|
|
||||||
|
|
||||||
# build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
|
|
||||||
# we use uid=1 because the visibility of a many2one field value (just id and name)
|
|
||||||
# must be the access right of the parent form and not the linked object itself.
|
|
||||||
obj = obj.pool.get(self._obj)
|
|
||||||
records = dict(obj.name_get(cr, 1,
|
|
||||||
list(set([x for x in result.values() if x and isinstance(x, (int,long))])),
|
|
||||||
context=context))
|
|
||||||
for id in ids:
|
|
||||||
if result[id] in records:
|
|
||||||
result[id] = (result[id], records[result[id]])
|
|
||||||
else:
|
|
||||||
result[id] = False
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
|
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
@ -447,55 +411,6 @@ class one2many(_column):
|
||||||
#one2many can't be used as condition for defaults
|
#one2many can't be used as condition for defaults
|
||||||
assert(self.change_default != True)
|
assert(self.change_default != True)
|
||||||
|
|
||||||
def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
if self._context:
|
|
||||||
context = context.copy()
|
|
||||||
context.update(self._context)
|
|
||||||
if not values:
|
|
||||||
values = {}
|
|
||||||
res = {}
|
|
||||||
for id in ids:
|
|
||||||
res[id] = []
|
|
||||||
ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
|
|
||||||
for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
|
|
||||||
if r[self._fields_id] in res:
|
|
||||||
res[r[self._fields_id]].append(r['id'])
|
|
||||||
return res
|
|
||||||
|
|
||||||
def set_memory(self, cr, obj, id, field, values, user=None, context=None):
|
|
||||||
if not context:
|
|
||||||
context = {}
|
|
||||||
if self._context:
|
|
||||||
context = context.copy()
|
|
||||||
context.update(self._context)
|
|
||||||
if not values:
|
|
||||||
return
|
|
||||||
obj = obj.pool.get(self._obj)
|
|
||||||
for act in values:
|
|
||||||
if act[0] == 0:
|
|
||||||
act[2][self._fields_id] = id
|
|
||||||
obj.create(cr, user, act[2], context=context)
|
|
||||||
elif act[0] == 1:
|
|
||||||
obj.write(cr, user, [act[1]], act[2], context=context)
|
|
||||||
elif act[0] == 2:
|
|
||||||
obj.unlink(cr, user, [act[1]], context=context)
|
|
||||||
elif act[0] == 3:
|
|
||||||
obj.datas[act[1]][self._fields_id] = False
|
|
||||||
elif act[0] == 4:
|
|
||||||
obj.datas[act[1]][self._fields_id] = id
|
|
||||||
elif act[0] == 5:
|
|
||||||
for o in obj.datas.values():
|
|
||||||
if o[self._fields_id] == id:
|
|
||||||
o[self._fields_id] = False
|
|
||||||
elif act[0] == 6:
|
|
||||||
for id2 in (act[2] or []):
|
|
||||||
obj.datas[id2][self._fields_id] = id
|
|
||||||
|
|
||||||
def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
|
|
||||||
raise _('Not Implemented')
|
|
||||||
|
|
||||||
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
@ -578,14 +493,34 @@ class one2many(_column):
|
||||||
# (6, ?, ids) set a list of links
|
# (6, ?, ids) set a list of links
|
||||||
#
|
#
|
||||||
class many2many(_column):
|
class many2many(_column):
|
||||||
|
"""Encapsulates the logic of a many-to-many bidirectional relationship, handling the
|
||||||
|
low-level details of the intermediary relationship table transparently.
|
||||||
|
|
||||||
|
:param str obj: destination model
|
||||||
|
:param str rel: optional name of the intermediary relationship table. If not specified,
|
||||||
|
a canonical name will be derived based on the alphabetically-ordered
|
||||||
|
model names of the source and destination (in the form: ``amodel_bmodel_rel``).
|
||||||
|
Automatic naming is not possible when the source and destination are
|
||||||
|
the same, for obvious ambiguity reasons.
|
||||||
|
:param str id1: optional name for the column holding the foreign key to the current
|
||||||
|
model in the relationship table. If not specified, a canonical name
|
||||||
|
will be derived based on the model name (in the form: `src_model_id`).
|
||||||
|
:param str id2: optional name for the column holding the foreign key to the destination
|
||||||
|
model in the relationship table. If not specified, a canonical name
|
||||||
|
will be derived based on the model name (in the form: `dest_model_id`)
|
||||||
|
:param str string: field label
|
||||||
|
"""
|
||||||
_classic_read = False
|
_classic_read = False
|
||||||
_classic_write = False
|
_classic_write = False
|
||||||
_prefetch = False
|
_prefetch = False
|
||||||
_type = 'many2many'
|
_type = 'many2many'
|
||||||
def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args):
|
|
||||||
|
def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
_column.__init__(self, string=string, **args)
|
_column.__init__(self, string=string, **args)
|
||||||
self._obj = obj
|
self._obj = obj
|
||||||
if '.' in rel:
|
if rel and '.' in rel:
|
||||||
raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
|
raise Exception(_('The second argument of the many2many field %s must be a SQL table !'\
|
||||||
'You used %s, which is not a valid SQL table name.')% (string,rel))
|
'You used %s, which is not a valid SQL table name.')% (string,rel))
|
||||||
self._rel = rel
|
self._rel = rel
|
||||||
|
@ -593,7 +528,30 @@ class many2many(_column):
|
||||||
self._id2 = id2
|
self._id2 = id2
|
||||||
self._limit = limit
|
self._limit = limit
|
||||||
|
|
||||||
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
def _sql_names(self, source_model):
|
||||||
|
"""Return the SQL names defining the structure of the m2m relationship table
|
||||||
|
|
||||||
|
:return: (m2m_table, local_col, dest_col) where m2m_table is the table name,
|
||||||
|
local_col is the name of the column holding the current model's FK, and
|
||||||
|
dest_col is the name of the column holding the destination model's FK, and
|
||||||
|
"""
|
||||||
|
tbl, col1, col2 = self._rel, self._id1, self._id2
|
||||||
|
if not all((tbl, col1, col2)):
|
||||||
|
# the default table name is based on the stable alphabetical order of tables
|
||||||
|
dest_model = source_model.pool.get(self._obj)
|
||||||
|
tables = tuple(sorted([source_model._table, dest_model._table]))
|
||||||
|
if not tbl:
|
||||||
|
assert tables[0] != tables[1], 'Implicit/Canonical naming of m2m relationship table '\
|
||||||
|
'is not possible when source and destination models are '\
|
||||||
|
'the same'
|
||||||
|
tbl = '%s_%s_rel' % tables
|
||||||
|
if not col1:
|
||||||
|
col1 = '%s_id' % source_model._table
|
||||||
|
if not col2:
|
||||||
|
col2 = '%s_id' % dest_model._table
|
||||||
|
return (tbl, col1, col2)
|
||||||
|
|
||||||
|
def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None):
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
if not values:
|
if not values:
|
||||||
|
@ -606,7 +564,8 @@ class many2many(_column):
|
||||||
if offset:
|
if offset:
|
||||||
warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
|
warnings.warn("Specifying offset at a many2many.get() may produce unpredictable results.",
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
obj = obj.pool.get(self._obj)
|
obj = model.pool.get(self._obj)
|
||||||
|
rel, id1, id2 = self._sql_names(model)
|
||||||
|
|
||||||
# static domains are lists, and are evaluated both here and on client-side, while string
|
# static domains are lists, and are evaluated both here and on client-side, while string
|
||||||
# domains supposed by dynamic and evaluated on client-side only (thus ignored here)
|
# domains supposed by dynamic and evaluated on client-side only (thus ignored here)
|
||||||
|
@ -636,11 +595,11 @@ class many2many(_column):
|
||||||
%(order_by)s \
|
%(order_by)s \
|
||||||
%(limit)s \
|
%(limit)s \
|
||||||
OFFSET %(offset)d' \
|
OFFSET %(offset)d' \
|
||||||
% {'rel': self._rel,
|
% {'rel': rel,
|
||||||
'from_c': from_c,
|
'from_c': from_c,
|
||||||
'tbl': obj._table,
|
'tbl': obj._table,
|
||||||
'id1': self._id1,
|
'id1': id1,
|
||||||
'id2': self._id2,
|
'id2': id2,
|
||||||
'where_c': where_c,
|
'where_c': where_c,
|
||||||
'limit': limit_str,
|
'limit': limit_str,
|
||||||
'order_by': order_by,
|
'order_by': order_by,
|
||||||
|
@ -651,31 +610,32 @@ class many2many(_column):
|
||||||
res[r[1]].append(r[0])
|
res[r[1]].append(r[0])
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def set(self, cr, obj, id, name, values, user=None, context=None):
|
def set(self, cr, model, id, name, values, user=None, context=None):
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
if not values:
|
if not values:
|
||||||
return
|
return
|
||||||
obj = obj.pool.get(self._obj)
|
rel, id1, id2 = self._sql_names(model)
|
||||||
|
obj = model.pool.get(self._obj)
|
||||||
for act in values:
|
for act in values:
|
||||||
if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
|
if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
|
||||||
continue
|
continue
|
||||||
if act[0] == 0:
|
if act[0] == 0:
|
||||||
idnew = obj.create(cr, user, act[2], context=context)
|
idnew = obj.create(cr, user, act[2], context=context)
|
||||||
cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, idnew))
|
cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, idnew))
|
||||||
elif act[0] == 1:
|
elif act[0] == 1:
|
||||||
obj.write(cr, user, [act[1]], act[2], context=context)
|
obj.write(cr, user, [act[1]], act[2], context=context)
|
||||||
elif act[0] == 2:
|
elif act[0] == 2:
|
||||||
obj.unlink(cr, user, [act[1]], context=context)
|
obj.unlink(cr, user, [act[1]], context=context)
|
||||||
elif act[0] == 3:
|
elif act[0] == 3:
|
||||||
cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%s and '+ self._id2 + '=%s', (id, act[1]))
|
cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1]))
|
||||||
elif act[0] == 4:
|
elif act[0] == 4:
|
||||||
# following queries are in the same transaction - so should be relatively safe
|
# following queries are in the same transaction - so should be relatively safe
|
||||||
cr.execute('SELECT 1 FROM '+self._rel+' WHERE '+self._id1+' = %s and '+self._id2+' = %s', (id, act[1]))
|
cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1]))
|
||||||
if not cr.fetchone():
|
if not cr.fetchone():
|
||||||
cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s,%s)', (id, act[1]))
|
cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1]))
|
||||||
elif act[0] == 5:
|
elif act[0] == 5:
|
||||||
cr.execute('delete from '+self._rel+' where ' + self._id1 + ' = %s', (id,))
|
cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,))
|
||||||
elif act[0] == 6:
|
elif act[0] == 6:
|
||||||
|
|
||||||
d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
|
d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
|
||||||
|
@ -683,10 +643,10 @@ class many2many(_column):
|
||||||
d1 = ' and ' + ' and '.join(d1)
|
d1 = ' and ' + ' and '.join(d1)
|
||||||
else:
|
else:
|
||||||
d1 = ''
|
d1 = ''
|
||||||
cr.execute('delete from '+self._rel+' where '+self._id1+'=%s AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+','.join(tables)+' WHERE '+self._rel+'.'+self._id1+'=%s AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2)
|
cr.execute('delete from '+rel+' where '+id1+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2)
|
||||||
|
|
||||||
for act_nbr in act[2]:
|
for act_nbr in act[2]:
|
||||||
cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%s, %s)', (id, act_nbr))
|
cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr))
|
||||||
|
|
||||||
#
|
#
|
||||||
# TODO: use a name_search
|
# TODO: use a name_search
|
||||||
|
@ -694,32 +654,6 @@ class many2many(_column):
|
||||||
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
|
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
|
||||||
return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
|
return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context)
|
||||||
|
|
||||||
def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
|
||||||
result = {}
|
|
||||||
for id in ids:
|
|
||||||
result[id] = obj.datas[id].get(name, [])
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_memory(self, cr, obj, id, name, values, user=None, context=None):
|
|
||||||
if not values:
|
|
||||||
return
|
|
||||||
for act in values:
|
|
||||||
# TODO: use constants instead of these magic numbers
|
|
||||||
if act[0] == 0:
|
|
||||||
raise _('Not Implemented')
|
|
||||||
elif act[0] == 1:
|
|
||||||
raise _('Not Implemented')
|
|
||||||
elif act[0] == 2:
|
|
||||||
raise _('Not Implemented')
|
|
||||||
elif act[0] == 3:
|
|
||||||
raise _('Not Implemented')
|
|
||||||
elif act[0] == 4:
|
|
||||||
raise _('Not Implemented')
|
|
||||||
elif act[0] == 5:
|
|
||||||
raise _('Not Implemented')
|
|
||||||
elif act[0] == 6:
|
|
||||||
obj.datas[id][name] = act[2]
|
|
||||||
|
|
||||||
|
|
||||||
def get_nice_size(value):
|
def get_nice_size(value):
|
||||||
size = 0
|
size = 0
|
||||||
|
@ -801,8 +735,8 @@ class function(_column):
|
||||||
|
|
||||||
Implements the function field.
|
Implements the function field.
|
||||||
|
|
||||||
:param orm_template model: model to which the field belongs (should be ``self`` for
|
:param orm model: model to which the field belongs (should be ``self`` for
|
||||||
a model method)
|
a model method)
|
||||||
:param field_name(s): name of the field to compute, or if ``multi`` is provided,
|
:param field_name(s): name of the field to compute, or if ``multi`` is provided,
|
||||||
list of field names to compute.
|
list of field names to compute.
|
||||||
:type field_name(s): str | [str]
|
:type field_name(s): str | [str]
|
||||||
|
@ -865,8 +799,8 @@ class function(_column):
|
||||||
|
|
||||||
Callable that implements the ``write`` operation for the function field.
|
Callable that implements the ``write`` operation for the function field.
|
||||||
|
|
||||||
:param orm_template model: model to which the field belongs (should be ``self`` for
|
:param orm model: model to which the field belongs (should be ``self`` for
|
||||||
a model method)
|
a model method)
|
||||||
:param int id: the identifier of the object to write on
|
:param int id: the identifier of the object to write on
|
||||||
:param str field_name: name of the field to set
|
:param str field_name: name of the field to set
|
||||||
:param fnct_inv_arg: arbitrary value passed when declaring the function field
|
:param fnct_inv_arg: arbitrary value passed when declaring the function field
|
||||||
|
@ -887,10 +821,10 @@ class function(_column):
|
||||||
a search criterion based on the function field into a new domain based only on
|
a search criterion based on the function field into a new domain based only on
|
||||||
columns that are stored in the database.
|
columns that are stored in the database.
|
||||||
|
|
||||||
:param orm_template model: model to which the field belongs (should be ``self`` for
|
:param orm model: model to which the field belongs (should be ``self`` for
|
||||||
a model method)
|
a model method)
|
||||||
:param orm_template model_again: same value as ``model`` (seriously! this is for backwards
|
:param orm model_again: same value as ``model`` (seriously! this is for backwards
|
||||||
compatibility)
|
compatibility)
|
||||||
:param str field_name: name of the field to search on
|
:param str field_name: name of the field to search on
|
||||||
:param list criterion: domain component specifying the search criterion on the field.
|
:param list criterion: domain component specifying the search criterion on the field.
|
||||||
:rtype: list
|
:rtype: list
|
||||||
|
@ -935,7 +869,7 @@ class function(_column):
|
||||||
corresponding records in the source model (whose field values
|
corresponding records in the source model (whose field values
|
||||||
need to be recomputed).
|
need to be recomputed).
|
||||||
|
|
||||||
:param orm_template model: trigger_model
|
:param orm model: trigger_model
|
||||||
:param list trigger_ids: ids of the records of trigger_model that were
|
:param list trigger_ids: ids of the records of trigger_model that were
|
||||||
modified
|
modified
|
||||||
:rtype: list
|
:rtype: list
|
||||||
|
@ -1064,14 +998,11 @@ class function(_column):
|
||||||
result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
|
result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
get_memory = get
|
|
||||||
|
|
||||||
def set(self, cr, obj, id, name, value, user=None, context=None):
|
def set(self, cr, obj, id, name, value, user=None, context=None):
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
if self._fnct_inv:
|
if self._fnct_inv:
|
||||||
self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
|
self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
|
||||||
set_memory = set
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Related fields
|
# Related fields
|
||||||
|
|
1015
openerp/osv/orm.py
1015
openerp/osv/orm.py
File diff suppressed because it is too large
Load Diff
|
@ -21,16 +21,17 @@
|
||||||
|
|
||||||
#.apidoc title: Objects Services (OSV)
|
#.apidoc title: Objects Services (OSV)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from psycopg2 import IntegrityError, errorcodes
|
||||||
|
|
||||||
import orm
|
import orm
|
||||||
|
import openerp
|
||||||
import openerp.netsvc as netsvc
|
import openerp.netsvc as netsvc
|
||||||
import openerp.pooler as pooler
|
import openerp.pooler as pooler
|
||||||
import openerp.sql_db as sql_db
|
import openerp.sql_db as sql_db
|
||||||
import logging
|
|
||||||
from psycopg2 import IntegrityError, errorcodes
|
|
||||||
from openerp.tools.func import wraps
|
from openerp.tools.func import wraps
|
||||||
from openerp.tools.translate import translate
|
from openerp.tools.translate import translate
|
||||||
from openerp.osv.orm import MetaModel
|
from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel
|
||||||
|
|
||||||
|
|
||||||
class except_osv(Exception):
|
class except_osv(Exception):
|
||||||
def __init__(self, name, value, exc_type='warning'):
|
def __init__(self, name, value, exc_type='warning'):
|
||||||
|
@ -198,17 +199,10 @@ class object_proxy():
|
||||||
cr.close()
|
cr.close()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
# deprecated - for backward compatibility.
|
||||||
class osv_memory(orm.orm_memory):
|
osv = Model
|
||||||
""" Deprecated class. """
|
osv_memory = TransientModel
|
||||||
__metaclass__ = MetaModel
|
osv_abstract = AbstractModel # ;-)
|
||||||
_register = False # Set to false if the model shouldn't be automatically discovered.
|
|
||||||
|
|
||||||
|
|
||||||
class osv(orm.orm):
|
|
||||||
""" Deprecated class. """
|
|
||||||
__metaclass__ = MetaModel
|
|
||||||
_register = False # Set to false if the model shouldn't be automatically discovered.
|
|
||||||
|
|
||||||
|
|
||||||
def start_object_proxy():
|
def start_object_proxy():
|
||||||
|
|
|
@ -307,7 +307,7 @@ class YamlInterpreter(object):
|
||||||
import openerp.osv as osv
|
import openerp.osv as osv
|
||||||
record, fields = node.items()[0]
|
record, fields = node.items()[0]
|
||||||
model = self.get_model(record.model)
|
model = self.get_model(record.model)
|
||||||
if isinstance(model, osv.osv.osv_memory):
|
if model.is_transient():
|
||||||
record_dict=self.create_osv_memory_record(record, fields)
|
record_dict=self.create_osv_memory_record(record, fields)
|
||||||
else:
|
else:
|
||||||
self.validate_xml_id(record.id)
|
self.validate_xml_id(record.id)
|
||||||
|
|
Loading…
Reference in New Issue