-
+
months
diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py
index ac06e41b1c4..2d3d09111b3 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity.py
+++ b/addons/crm/wizard/crm_lead_to_opportunity.py
@@ -67,7 +67,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email, include_lost=True, context=context))
tomerge = list(set(tomerge))
- if 'action' in fields:
+ if 'action' in fields and not res.get('action'):
res.update({'action' : partner_id and 'exist' or 'create'})
if 'partner_id' in fields:
res.update({'partner_id' : partner_id})
diff --git a/addons/crm/wizard/crm_partner_binding.py b/addons/crm/wizard/crm_partner_binding.py
index 03c1c901b6b..a7e4977a866 100644
--- a/addons/crm/wizard/crm_partner_binding.py
+++ b/addons/crm/wizard/crm_partner_binding.py
@@ -89,7 +89,7 @@ class crm_partner_binding(osv.osv_memory):
res = super(crm_partner_binding, self).default_get(cr, uid, fields, context=context)
partner_id = self._find_matching_partner(cr, uid, context=context)
- if 'action' in fields:
+ if 'action' in fields and not res.get('action'):
res['action'] = partner_id and 'exist' or 'create'
if 'partner_id' in fields:
res['partner_id'] = partner_id
diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py
index 22f054eb7a9..699f2e28d83 100644
--- a/addons/gamification/models/challenge.py
+++ b/addons/gamification/models/challenge.py
@@ -301,18 +301,23 @@ class gamification_challenge(osv.Model):
goal_obj = self.pool.get('gamification.goal')
- # we use yesterday to update the goals that just ended
+ # include yesterday goals to update the goals that just ended
+ # exclude goals for users that did not connect since the last update
yesterday = date.today() - timedelta(days=1)
- goal_ids = goal_obj.search(cr, uid, [
- ('challenge_id', 'in', ids),
- '|',
- ('state', '=', 'inprogress'),
- '&',
- ('state', 'in', ('reached', 'failed')),
- '|',
- ('end_date', '>=', yesterday.strftime(DF)),
- ('end_date', '=', False)
- ], context=context)
+ cr.execute("""SELECT gg.id
+ FROM gamification_goal as gg,
+ gamification_challenge as gc,
+ res_users as ru
+ WHERE gg.challenge_id = gc.id
+ AND gg.user_id = ru.id
+ AND gg.write_date < ru.login_date
+ AND gg.closed IS false
+ AND gc.id IN %s
+ AND (gg.state = 'inprogress'
+ OR (gg.state = 'reached'
+ AND (gg.end_date >= %s OR gg.end_date IS NULL)))
+ """, (tuple(ids), yesterday.strftime(DF)))
+ goal_ids = [res[0] for res in cr.fetchall()]
# update every running goal already generated linked to selected challenges
goal_obj.update(cr, uid, goal_ids, context=context)
diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py
index 48adf095ac5..ec6f1f9dcb8 100644
--- a/addons/gamification/models/goal.py
+++ b/addons/gamification/models/goal.py
@@ -269,6 +269,25 @@ class gamification_goal(osv.Model):
return {'to_update': True}
return {}
+ def _get_write_values(self, cr, uid, goal, new_value, context=None):
+ """Generate values to write after recomputation of a goal score"""
+ if new_value == goal.current:
+ # avoid useless write if the new value is the same as the old one
+ return {}
+
+ result = {goal.id: {'current': new_value}}
+ if (goal.definition_id.condition == 'higher' and new_value >= goal.target_goal) \
+ or (goal.definition_id.condition == 'lower' and new_value <= goal.target_goal):
+ # success, do no set closed as can still change
+ result[goal.id]['state'] = 'reached'
+
+ elif goal.end_date and fields.date.today() > goal.end_date:
+ # check goal failure
+ result[goal.id]['state'] = 'failed'
+ result[goal.id]['closed'] = True
+
+ return result
+
def update(self, cr, uid, ids, context=None):
"""Update the goals to recomputes values and change of states
@@ -282,14 +301,8 @@ class gamification_goal(osv.Model):
commit = context.get('commit_gamification', False)
goals_by_definition = {}
- all_goals = {}
for goal in self.browse(cr, uid, ids, context=context):
- if goal.state in ('draft', 'canceled'):
- # draft or canceled goals should not be recomputed
- continue
-
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
- all_goals[goal.id] = goal
for definition, goals in goals_by_definition.items():
goals_to_write = dict((goal.id, {}) for goal in goals)
@@ -314,10 +327,12 @@ class gamification_goal(osv.Model):
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
result = cxt.get('result')
if result is not None and type(result) in (float, int, long):
- if result != goal.current:
- goals_to_write[goal.id]['current'] = result
+ goals_to_write.update(
+ self._get_write_values(cr, uid, goal, result, context=context)
+ )
+
else:
- _logger.exception(_('Invalid return content from the evaluation of code for definition %s' % definition.name))
+ _logger.exception(_('Invalid return content from the evaluation of code for definition %s') % definition.name)
else: # count or sum
@@ -357,8 +372,9 @@ class gamification_goal(osv.Model):
queried_value = queried_value[0]
if queried_value == query_goals[goal.id]:
new_value = user_value.get(field_name+'_count', goal.current)
- if new_value != goal.current:
- goals_to_write[goal.id]['current'] = new_value
+ goals_to_write.update(
+ self._get_write_values(cr, uid, goal, new_value, context=context)
+ )
else:
for goal in goals:
@@ -380,26 +396,14 @@ class gamification_goal(osv.Model):
else: # computation mode = count
new_value = obj.search(cr, uid, domain, context=context, count=True)
- # avoid useless write if the new value is the same as the old one
- if new_value != goal.current:
- goals_to_write[goal.id]['current'] = new_value
+ goals_to_write.update(
+ self._get_write_values(cr, uid, goal, new_value, context=context)
+ )
for goal_id, value in goals_to_write.items():
if not value:
continue
- goal = all_goals[goal_id]
-
- # check goal target reached
- if (goal.definition_id.condition == 'higher' and value.get('current', goal.current) >= goal.target_goal) \
- or (goal.definition_id.condition == 'lower' and value.get('current', goal.current) <= goal.target_goal):
- value['state'] = 'reached'
-
- # check goal failure
- elif goal.end_date and fields.date.today() > goal.end_date:
- value['state'] = 'failed'
- value['closed'] = True
- if value:
- self.write(cr, uid, [goal.id], value, context=context)
+ self.write(cr, uid, [goal_id], value, context=context)
if commit:
cr.commit()
return True
diff --git a/addons/google_calendar/res_config.py b/addons/google_calendar/res_config.py
index dbb8a633bf8..fb34d58cbe9 100644
--- a/addons/google_calendar/res_config.py
+++ b/addons/google_calendar/res_config.py
@@ -13,8 +13,8 @@ class calendar_config_settings(osv.TransientModel):
def set_calset(self,cr,uid,ids,context=None) :
params = self.pool['ir.config_parameter']
myself = self.browse(cr,uid,ids[0],context=context)
- params.set_param(cr, uid, 'google_calendar_client_id', myself.cal_client_id.strip() or '', groups=['base.group_system'], context=None)
- params.set_param(cr, uid, 'google_calendar_client_secret', myself.cal_client_secret.strip() or '', groups=['base.group_system'], context=None)
+ params.set_param(cr, uid, 'google_calendar_client_id', (myself.cal_client_id or '').strip(), groups=['base.group_system'], context=None)
+ params.set_param(cr, uid, 'google_calendar_client_secret', (myself.cal_client_secret or '').strip(), groups=['base.group_system'], context=None)
def get_default_all(self,cr,uid,ids,context=None):
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index fe2e0e78708..d68dbac1942 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1088,6 +1088,7 @@ class stock_picking(osv.osv):
# Create the necessary operations for the grouped quants and remaining qtys
uom_obj = self.pool.get('product.uom')
+ prevals = {}
for key, qty in qtys_grouped.items():
product = self.pool.get("product.product").browse(cr, uid, key[0], context=context)
uom_id = product.uom_id.id
@@ -1095,7 +1096,7 @@ class stock_picking(osv.osv):
if product_uom.get(key[0]):
uom_id = product_uom[key[0]].id
qty_uom = uom_obj._compute_qty(cr, uid, product.uom_id.id, qty, uom_id)
- vals.append({
+ val_dict = {
'picking_id': picking.id,
'product_qty': qty_uom,
'product_id': key[0],
@@ -1105,7 +1106,17 @@ class stock_picking(osv.osv):
'location_id': key[4],
'location_dest_id': key[5],
'product_uom_id': uom_id,
- })
+ }
+ if key[0] in prevals:
+ prevals[key[0]].append(val_dict)
+ else:
+ prevals[key[0]] = [val_dict]
+ # prevals var holds the operations in order to create them in the same order than the picking stock moves if possible
+ processed_products = set()
+ for move in picking.move_lines:
+ if move.product_id.id not in processed_products:
+ vals += prevals.get(move.product_id.id, [])
+ processed_products.add(move.product_id.id)
return vals
@api.cr_uid_ids_context
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 0cafc63d829..7e42441b877 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -600,12 +600,9 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.do_notify(_t("Backed"), _t("Database backed up successfully"));
},
error: function(error){
- if(error){
- self.display_error({
- title: _t("Backup Database"),
- error: 'AccessDenied'
- });
- }
+ if (error && error[1]) {
+ self.display_error(error[1][0]);
+ }
},
complete: function() {
self.unblockUI();
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 2783b85780c..c87fa98b15c 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -2615,6 +2615,9 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
// As in GTK client, floats default to 0
value_ = 0;
}
+ if (this.digits !== undefined && this.digits.length === 2) {
+ value_ = instance.web.round_precision(value_, this.digits[1]);
+ }
this._super.apply(this, [value_]);
},
focus: function () {
diff --git a/addons/website_sale/security/ir.model.access.csv b/addons/website_sale/security/ir.model.access.csv
index c1774fffe2c..2101397bf60 100644
--- a/addons/website_sale/security/ir.model.access.csv
+++ b/addons/website_sale/security/ir.model.access.csv
@@ -10,7 +10,7 @@ access_product_pricelist_item_public,product.pricelist.item.public,product.model
access_product_product_price_type_public,product.price.type.public,product.model_product_price_type,,1,0,0,0
access_product_style,product.style.public,website_sale.model_product_style,,1,0,0,0
access_product_supplierinfo,product.supplierinfo.public,product.model_product_supplierinfo,,1,0,0,0
-access_product_attribute_public,product.attribute public,product.model_product_attribute,base.group_public,1,0,0,0
-access_product_attribute_value_public,product.attribute value public,product.model_product_attribute_value,base.group_public,1,0,0,0
-access_product_attribute_price_public,product.attribute price public,product.model_product_attribute_price,base.group_public,1,0,0,0
-access_product_attribute_line_public,product.attribute line public,product.model_product_attribute_line,base.group_public,1,0,0,0
+access_product_attribute_public,product.attribute public,product.model_product_attribute,,1,0,0,0
+access_product_attribute_value_public,product.attribute value public,product.model_product_attribute_value,,1,0,0,0
+access_product_attribute_price_public,product.attribute price public,product.model_product_attribute_price,,1,0,0,0
+access_product_attribute_line_public,product.attribute line public,product.model_product_attribute_line,,1,0,0,0
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 77e07c8b672..96f351b9ee3 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -387,9 +387,7 @@ class ir_model_fields(osv.osv):
if vals['model'].startswith('x_') and vals['name'] == 'x_name':
model._rec_name = 'x_name'
- if self.pool.fields_by_model is not None:
- cr.execute('SELECT * FROM ir_model_fields WHERE id=%s', (res,))
- self.pool.fields_by_model.setdefault(vals['model'], []).append(cr.dictfetchone())
+ self.pool.clear_manual_fields()
# re-initialize model in registry
model.__init__(self.pool, cr)
diff --git a/openerp/addons/test_impex/tests/test_load.py b/openerp/addons/test_impex/tests/test_load.py
index d9cbd5b100a..2fcf6dc2ad6 100644
--- a/openerp/addons/test_impex/tests/test_load.py
+++ b/openerp/addons/test_impex/tests/test_load.py
@@ -642,12 +642,11 @@ class test_m2o(ImporterCase):
@mute_logger('openerp.sql_db')
def test_fail_id_mistype(self):
result = self.import_(['value/.id'], [["foo"]])
-
- self.assertEqual(result['messages'], [
- message(u"Invalid database id 'foo' for the field 'unknown'",
- moreinfo=moreaction(res_model='ir.model.data',
- domain=[('model','=','export.integer')]))
- ])
+ try:
+ int("foo")
+ except ValueError, exc:
+ expected_message = unicode(exc)
+ self.assertEqual(result['messages'], [message(expected_message)])
self.assertIs(result['ids'], False)
def test_sub_field(self):
diff --git a/openerp/fields.py b/openerp/fields.py
index 030872c0092..b371e257624 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -336,8 +336,6 @@ class Field(object):
# determine self.default and cls._defaults in a consistent way
self._determine_default(cls, name)
- self.reset()
-
def _determine_default(self, cls, name):
""" Retrieve the default value for `self` in the hierarchy of `cls`, and
determine `self.default` and `cls._defaults` accordingly.
@@ -403,33 +401,40 @@ class Field(object):
self.inverse_fields = []
def setup(self, env):
- """ Complete the setup of `self` (dependencies, recomputation triggers,
- and other properties). This method is idempotent: it has no effect
- if `self` has already been set up.
- """
+ """ Make sure that `self` is set up, except for recomputation triggers. """
if not self.setup_done:
- self._setup(env)
+ if self.related:
+ self._setup_related(env)
+ else:
+ self._setup_regular(env)
self.setup_done = True
- def _setup(self, env):
- """ Do the actual setup of `self`. """
- if self.related:
- self._setup_related(env)
+ #
+ # Setup of non-related fields
+ #
+
+ def _setup_regular(self, env):
+ """ Setup the attributes of a non-related field. """
+ recs = env[self.model_name]
+
+ def make_depends(deps):
+ return tuple(deps(recs) if callable(deps) else deps)
+
+ # convert compute into a callable and determine depends
+ if isinstance(self.compute, basestring):
+ # if the compute method has been overridden, concatenate all their _depends
+ self.depends = ()
+ for method in resolve_all_mro(type(recs), self.compute, reverse=True):
+ self.depends += make_depends(getattr(method, '_depends', ()))
+ self.compute = getattr(type(recs), self.compute)
else:
- self._setup_regular(env)
+ self.depends = make_depends(getattr(self.compute, '_depends', ()))
- # put invalidation/recomputation triggers on field dependencies
- model = env[self.model_name]
- for path in self.depends:
- self._setup_dependency([], model, path.split('.'))
-
- # put invalidation triggers on model dependencies
- for dep_model_name, field_names in model._depends.iteritems():
- dep_model = env[dep_model_name]
- dep_model._setup_fields()
- for field_name in field_names:
- field = dep_model._fields[field_name]
- field._triggers.add((self, None))
+ # convert inverse and search into callables
+ if isinstance(self.inverse, basestring):
+ self.inverse = getattr(type(recs), self.inverse)
+ if isinstance(self.search, basestring):
+ self.search = getattr(type(recs), self.search)
#
# Setup of related fields
@@ -445,7 +450,6 @@ class Field(object):
recs = env[self.model_name]
fields = []
for name in self.related:
- recs._setup_fields()
field = recs._fields[name]
field.setup(env)
recs = recs[name]
@@ -524,31 +528,14 @@ class Field(object):
return self.related_field if self.inherited else self
#
- # Setup of non-related fields
+ # Setup of field triggers
#
- def _setup_regular(self, env):
- """ Setup the attributes of a non-related field. """
- recs = env[self.model_name]
-
- def make_depends(deps):
- return tuple(deps(recs) if callable(deps) else deps)
-
- # convert compute into a callable and determine depends
- if isinstance(self.compute, basestring):
- # if the compute method has been overridden, concatenate all their _depends
- self.depends = ()
- for method in resolve_all_mro(type(recs), self.compute, reverse=True):
- self.depends += make_depends(getattr(method, '_depends', ()))
- self.compute = getattr(type(recs), self.compute)
- else:
- self.depends = make_depends(getattr(self.compute, '_depends', ()))
-
- # convert inverse and search into callables
- if isinstance(self.inverse, basestring):
- self.inverse = getattr(type(recs), self.inverse)
- if isinstance(self.search, basestring):
- self.search = getattr(type(recs), self.search)
+ def setup_triggers(self, env):
+ """ Add the necessary triggers to invalidate/recompute `self`. """
+ model = env[self.model_name]
+ for path in self.depends:
+ self._setup_dependency([], model, path.split('.'))
def _setup_dependency(self, path0, model, path1):
""" Make `self` depend on `model`; `path0 + path1` is a dependency of
@@ -558,7 +545,6 @@ class Field(object):
env = model.env
head, tail = path1[0], path1[1:]
- model._setup_fields()
if head == '*':
# special case: add triggers on all fields of model (except self)
fields = set(model._fields.itervalues()) - set([self])
@@ -571,8 +557,6 @@ class Field(object):
self.recursive = True
continue
- field.setup(env)
-
#_logger.debug("Add trigger on %s to recompute %s", field, self)
field._triggers.add((self, '.'.join(path0 or ['id'])))
@@ -1050,8 +1034,8 @@ class Char(_String):
type = 'char'
size = None
- def _setup(self, env):
- super(Char, self)._setup(env)
+ def _setup_regular(self, env):
+ super(Char, self)._setup_regular(env)
assert isinstance(self.size, (NoneType, int)), \
"Char field %s with non-integer size %r" % (self, self.size)
@@ -1253,8 +1237,8 @@ class Selection(Field):
selection = api.expected(api.model, selection)
super(Selection, self).__init__(selection=selection, string=string, **kwargs)
- def _setup(self, env):
- super(Selection, self)._setup(env)
+ def _setup_regular(self, env):
+ super(Selection, self)._setup_regular(env)
assert self.selection is not None, "Field %s without selection" % self
def _setup_related(self, env):
@@ -1341,8 +1325,8 @@ class Reference(Selection):
def __init__(self, selection=None, string=None, **kwargs):
super(Reference, self).__init__(selection=selection, string=string, **kwargs)
- def _setup(self, env):
- super(Reference, self)._setup(env)
+ def _setup_regular(self, env):
+ super(Reference, self)._setup_regular(env)
assert isinstance(self.size, (NoneType, int)), \
"Reference field %s with non-integer size %r" % (self, self.size)
@@ -1378,8 +1362,8 @@ class _Relational(Field):
domain = None # domain for searching values
context = None # context for searching values
- def _setup(self, env):
- super(_Relational, self)._setup(env)
+ def _setup_regular(self, env):
+ super(_Relational, self)._setup_regular(env)
if self.comodel_name not in env.registry:
_logger.warning("Field %s with unknown comodel_name %r"
% (self, self.comodel_name))
@@ -1664,7 +1648,6 @@ class One2many(_RelationalMulti):
if self.inverse_name:
# link self to its inverse field and vice-versa
comodel = env[self.comodel_name]
- comodel._setup_fields()
invf = comodel._fields[self.inverse_name]
# In some rare cases, a `One2many` field can link to `Int` field
# (res_model/res_id pattern). Only inverse the field if this is
diff --git a/openerp/models.py b/openerp/models.py
index eec8f99e17b..6e084dde3da 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -476,11 +476,7 @@ class BaseModel(object):
# basic setup of field
field.set_class_name(cls, name)
- if field.store or field.column:
- cls._columns[name] = field.to_column()
- else:
- # remove potential column that may be overridden by field
- cls._columns.pop(name, None)
+ # cls._columns will be updated once fields are set up
@classmethod
def _pop_field(cls, name):
@@ -600,13 +596,6 @@ class BaseModel(object):
# inferred metadata; use its ancestor instead
parent_class = type(parent_model).__base__
- # don't inherit custom fields
- columns = dict((key, val)
- for key, val in parent_class._columns.iteritems()
- if not val.manual
- )
- columns.update(cls._columns)
-
inherits = dict(parent_class._inherits)
inherits.update(cls._inherits)
@@ -630,7 +619,6 @@ class BaseModel(object):
attrs = {
'_name': name,
'_register': False,
- '_columns': columns,
'_inherits': inherits,
'_depends': depends,
'_constraints': constraints,
@@ -643,7 +631,7 @@ class BaseModel(object):
attrs = {
'_name': name,
'_register': False,
- '_columns': dict(cls._columns),
+ '_columns': {}, # filled by _setup_fields()
'_defaults': {}, # filled by Field._determine_default()
'_inherits': dict(cls._inherits),
'_depends': dict(cls._depends),
@@ -699,16 +687,11 @@ class BaseModel(object):
pool._store_function[model].sort(key=lambda x: x[4])
@classmethod
- def _init_manual_fields(cls, cr, partial=False):
- # Check whether the query is already done
- if cls.pool.fields_by_model is not None:
- manual_fields = cls.pool.fields_by_model.get(cls._name, [])
- else:
- cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (cls._name, 'manual'))
- manual_fields = cr.dictfetchall()
+ def _init_manual_fields(cls, cr, partial):
+ manual_fields = cls.pool.get_manual_fields(cr, cls._name)
- for field in manual_fields:
- if field['name'] in cls._fields:
+ for name, field in manual_fields.iteritems():
+ if name in cls._fields:
continue
attrs = {
'manual': True,
@@ -729,7 +712,11 @@ class BaseModel(object):
attrs['ondelete'] = field['on_delete']
attrs['domain'] = eval(field['domain']) if field['domain'] else None
elif field['ttype'] == 'one2many':
- if partial and field['relation'] not in cls.pool:
+ if partial and not (
+ field['relation'] in cls.pool and (
+ field['relation_field'] in cls.pool[field['relation']]._fields or
+ field['relation_field'] in cls.pool.get_manual_fields(cr, field['relation'])
+ )):
continue
attrs['comodel_name'] = field['relation']
attrs['inverse_name'] = field['relation_field']
@@ -740,11 +727,11 @@ class BaseModel(object):
attrs['comodel_name'] = field['relation']
_rel1 = field['relation'].replace('.', '_')
_rel2 = field['model'].replace('.', '_')
- attrs['relation'] = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
+ attrs['relation'] = 'x_%s_%s_%s_rel' % (_rel1, _rel2, name)
attrs['column1'] = 'id1'
attrs['column2'] = 'id2'
attrs['domain'] = eval(field['domain']) if field['domain'] else None
- cls._add_field(field['name'], Field.by_type[field['ttype']](**attrs))
+ cls._add_field(name, Field.by_type[field['ttype']](**attrs))
@classmethod
def _init_constraints_onchanges(cls):
@@ -817,9 +804,6 @@ class BaseModel(object):
# introduce magic fields
cls._add_magic_fields()
- # register stuff about low-level function fields and custom fields
- cls._init_function_fields(pool, cr)
-
# register constraints and onchange methods
cls._init_constraints_onchanges()
@@ -1700,7 +1684,7 @@ class BaseModel(object):
return False
@api.model
- def name_search(self, name='', args=None, operator='ilike', limit=100):
+ def name_search(self, name='', args=None, operator=None, limit=100):
""" name_search(name='', args=None, operator='ilike', limit=100) -> records
Search for records that have a display name matching the given
@@ -1726,7 +1710,7 @@ class BaseModel(object):
"""
return self._name_search(name, args, operator, limit=limit)
- def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
+ def _name_search(self, cr, user, name='', args=None, operator=None, context=None, limit=100, name_get_uid=None):
# private implementation of name_search, allows passing a dedicated user
# for the name_get part to solve some access rights issues
args = list(args or [])
@@ -1734,6 +1718,9 @@ class BaseModel(object):
if not self._rec_name:
_logger.warning("Cannot execute name_search, no _rec_name defined on %s", self._name)
elif not (name == '' and operator == 'ilike'):
+ if operator is None:
+ field = self._fields[self._rec_name]
+ operator = ('=' if field.type == 'integer' else 'ilike')
args += [(self._rec_name, operator, name)]
access_rights_uid = name_get_uid or user
ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid)
@@ -2876,18 +2863,12 @@ class BaseModel(object):
#
@classmethod
- def _inherits_reload(cls):
- """ Recompute the _inherit_fields mapping, and inherited fields. """
- struct = {}
+ def _init_inherited_fields(cls):
+ """ Determine inherited fields. """
+ # determine candidate inherited fields
fields = {}
for parent_model, parent_field in cls._inherits.iteritems():
parent = cls.pool[parent_model]
- # old-api struct for _inherit_fields
- for name, column in parent._columns.iteritems():
- struct[name] = (parent_model, parent_field, column, parent_model)
- for name, source in parent._inherit_fields.iteritems():
- struct[name] = (parent_model, parent_field, source[2], source[3])
- # new-api fields for _fields
for name, field in parent._fields.iteritems():
fields[name] = field.new(
inherited=True,
@@ -2895,15 +2876,25 @@ class BaseModel(object):
related_sudo=False,
)
- # old-api stuff
- cls._inherit_fields = struct
- cls._all_columns = cls._get_column_infos()
-
# add inherited fields that are not redefined locally
for name, field in fields.iteritems():
if name not in cls._fields:
cls._add_field(name, field)
+ @classmethod
+ def _inherits_reload(cls):
+ """ Recompute the _inherit_fields and _all_columns mappings. """
+ cls._inherit_fields = struct = {}
+ for parent_model, parent_field in cls._inherits.iteritems():
+ parent = cls.pool[parent_model]
+ for name, column in parent._columns.iteritems():
+ struct[name] = (parent_model, parent_field, column, parent_model)
+ for name, source in parent._inherit_fields.iteritems():
+ struct[name] = (parent_model, parent_field, source[2], source[3])
+
+ # old-api stuff
+ cls._all_columns = cls._get_column_infos()
+
@classmethod
def _get_column_infos(cls):
"""Returns a dict mapping all fields names (direct fields and
@@ -2920,14 +2911,16 @@ class BaseModel(object):
@classmethod
def _inherits_check(cls):
for table, field_name in cls._inherits.items():
- if field_name not in cls._columns:
+ field = cls._fields.get(field_name)
+ if not field:
_logger.info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.', field_name, cls._name)
- cls._columns[field_name] = fields.many2one(table, string="Automatically created field to link to parent %s" % table,
- required=True, ondelete="cascade")
- elif not cls._columns[field_name].required or cls._columns[field_name].ondelete.lower() not in ("cascade", "restrict"):
+ from .fields import Many2one
+ field = Many2one(table, string="Automatically created field to link to parent %s" % table, required=True, ondelete="cascade")
+ cls._add_field(field_name, field)
+ elif not field.required or field.ondelete.lower() not in ("cascade", "restrict"):
_logger.warning('Field definition for _inherits reference "%s" in "%s" must be marked as "required" with ondelete="cascade" or "restrict", forcing it to required + cascade.', field_name, cls._name)
- cls._columns[field_name].required = True
- cls._columns[field_name].ondelete = "cascade"
+ field.required = True
+ field.ondelete = "cascade"
# reflect fields with delegate=True in dictionary cls._inherits
for field in cls._fields.itervalues():
@@ -2940,44 +2933,48 @@ class BaseModel(object):
cls._inherits[field.comodel_name] = field.name
@api.model
- def _prepare_setup_fields(self):
- """ Prepare the setup of fields once the models have been loaded. """
+ def _prepare_setup(self):
+ """ Prepare the setup of the model. """
type(self)._setup_done = False
- for name, field in self._fields.items():
- if field.inherited:
- del self._fields[name]
- else:
- field.reset()
@api.model
- def _setup_fields(self, partial=False):
- """ Setup the fields (dependency triggers, etc).
-
- :param partial: ``True`` if all models have not been loaded yet.
- """
+ def _setup_base(self, partial):
+ """ Determine the inherited and custom fields of the model. """
cls = type(self)
if cls._setup_done:
return
- cls._setup_done = True
- # first make sure that parent models are all set up
- for parent in self._inherits:
- self.env[parent]._setup_fields()
+ # first make sure that parent models determine all their fields
+ cls._inherits_check()
+ for parent in cls._inherits:
+ self.env[parent]._setup_base(partial)
+
+ # remove inherited fields from cls._fields
+ for name, field in cls._fields.items():
+ if field.inherited:
+ del cls._fields[name]
# retrieve custom fields
- cls._init_manual_fields(self._cr, partial=partial)
+ cls._init_manual_fields(self._cr, partial)
# retrieve inherited fields
- cls._inherits_check()
- cls._inherits_reload()
+ cls._init_inherited_fields()
- # set up fields
+ # prepare the setup of fields
for field in cls._fields.itervalues():
- field.setup(self.env)
+ field.reset()
- # update columns (fields may have changed)
+ cls._setup_done = True
+
+ @api.model
+ def _setup_fields(self):
+ """ Setup the fields, except for recomputation triggers. """
+ cls = type(self)
+
+ # set up fields, and update their corresponding columns
for name, field in cls._fields.iteritems():
- if field.column:
+ field.setup(self.env)
+ if field.store or field.column:
cls._columns[name] = field.to_column()
# group fields by compute to determine field.computed_fields
@@ -2989,6 +2986,30 @@ class BaseModel(object):
else:
field.computed_fields = []
+ @api.model
+ def _setup_complete(self):
+ """ Setup recomputation triggers, and complete the model setup. """
+ cls = type(self)
+
+ # set up field triggers
+ for field in cls._fields.itervalues():
+ field.setup_triggers(self.env)
+
+ # add invalidation triggers on model dependencies
+ if cls._depends:
+ triggers = [(field, None) for field in cls._fields.itervalues()]
+ for model_name, field_names in cls._depends.iteritems():
+ model = self.env[model_name]
+ for field_name in field_names:
+ field = model._fields[field_name]
+ field._triggers.update(triggers)
+
+ # determine old-api cls._inherit_fields and cls._all_columns
+ cls._inherits_reload()
+
+ # register stuff about low-level function fields
+ cls._init_function_fields(cls.pool, self._cr)
+
# check constraints
for func in cls._constraint_methods:
if not all(name in cls._fields for name in func._constrains):
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index 33475493115..29a7e3e68e6 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -125,13 +125,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
migrations = openerp.modules.migration.MigrationManager(cr, graph)
_logger.info('loading %d modules...', len(graph))
- # Query manual fields for all models at once and save them on the registry
- # so the initialization code for each model does not have to do it
- # one model at a time.
- registry.fields_by_model = {}
- cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
- for field in cr.dictfetchall():
- registry.fields_by_model.setdefault(field['model'], []).append(field)
+ registry.clear_manual_fields()
# register, instantiate and initialize models for each modules
t0 = time.time()
@@ -161,13 +155,6 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
registry.setup_models(cr, partial=True)
init_module_models(cr, package.name, models)
- # Can't put this line out of the loop: ir.module.module will be
- # registered by init_module_models() above.
- modobj = registry['ir.module.module']
-
- if perform_checks:
- modobj.check(cr, SUPERUSER_ID, [module_id])
-
idref = {}
mode = 'update'
@@ -175,6 +162,13 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
mode = 'init'
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
+ # Can't put this line out of the loop: ir.module.module will be
+ # registered by init_module_models() above.
+ modobj = registry['ir.module.module']
+
+ if perform_checks:
+ modobj.check(cr, SUPERUSER_ID, [module_id])
+
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
@@ -226,9 +220,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
_logger.log(25, "%s modules loaded in %.2fs, %s queries", len(graph), time.time() - t0, openerp.sql_db.sql_counter - t0_sql)
- # The query won't be valid for models created later (i.e. custom model
- # created after the registry has been loaded), so empty its result.
- registry.fields_by_model = None
+ registry.clear_manual_fields()
cr.commit()
diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py
index 47980eea091..2a3b28f6c8f 100644
--- a/openerp/modules/registry.py
+++ b/openerp/modules/registry.py
@@ -22,7 +22,7 @@
""" Models registries.
"""
-from collections import Mapping
+from collections import Mapping, defaultdict
import logging
import os
import threading
@@ -51,7 +51,7 @@ class Registry(Mapping):
self._init = True
self._init_parent = {}
self._assertion_report = assertion_report.assertion_report()
- self.fields_by_model = None
+ self._fields_by_model = None
# modules fully loaded (maintained during init phase by `loading` module)
self._init_modules = set()
@@ -114,6 +114,20 @@ class Registry(Mapping):
fields.append(model_fields[fname])
return fields
+ def clear_manual_fields(self):
+ """ Invalidate the cache for manual fields. """
+ self._fields_by_model = None
+
+ def get_manual_fields(self, cr, model_name):
+ """ Return the manual fields (as a dict) for the given model. """
+ if self._fields_by_model is None:
+ # Query manual fields for all models at once
+ self._fields_by_model = dic = defaultdict(dict)
+ cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
+ for field in cr.dictfetchall():
+ dic[field['model']][field['name']] = field
+ return self._fields_by_model[model_name]
+
def do_parent_store(self, cr):
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
@@ -166,12 +180,18 @@ class Registry(Mapping):
# prepare the setup on all models
for model in self.models.itervalues():
- model._prepare_setup_fields(cr, SUPERUSER_ID)
+ model._prepare_setup(cr, SUPERUSER_ID)
# do the actual setup from a clean state
self._m2m = {}
for model in self.models.itervalues():
- model._setup_fields(cr, SUPERUSER_ID, partial=partial)
+ model._setup_base(cr, SUPERUSER_ID, partial)
+
+ for model in self.models.itervalues():
+ model._setup_fields(cr, SUPERUSER_ID)
+
+ for model in self.models.itervalues():
+ model._setup_complete(cr, SUPERUSER_ID)
def clear_caches(self):
""" Clear the caches
diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py
index 0dbe417b814..81138c76c8b 100644
--- a/openerp/osv/expression.py
+++ b/openerp/osv/expression.py
@@ -1195,10 +1195,6 @@ class expression(object):
query, params = self.__leaf_to_sql(
create_substitution_leaf(eleaf, (left, '=', right), model))
- elif left == 'id':
- query = '%s.id %s %%s' % (table_alias, operator)
- params = right
-
else:
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py
index 11d0ebb7088..2f02eb8db2a 100644
--- a/openerp/tools/misc.py
+++ b/openerp/tools/misc.py
@@ -119,7 +119,7 @@ def exec_pg_command(name, *args):
prog = find_pg_tool(name)
env = exec_pg_environ()
with open(os.devnull) as dn:
- rc = subprocess.call((prog,) + args, stdout=dn, stderr=subprocess.STDOUT)
+ rc = subprocess.call((prog,) + args, env=env, stdout=dn, stderr=subprocess.STDOUT)
if rc:
raise Exception('Postgres subprocess %s error %s' % (args2, rc))
diff --git a/setup/package.dfcentos b/setup/package.dfcentos
index cd494534c51..1ad8141c887 100644
--- a/setup/package.dfcentos
+++ b/setup/package.dfcentos
@@ -42,8 +42,10 @@ RUN yum install -d 0 -e 0 epel-release -y && \
RUN easy_install pyPdf vatnumber pydot psycogreen
-# Manual start of postgres
+# Postgres configuration
RUN mkdir -p /var/lib/postgres/data
RUN chown -R postgres:postgres /var/lib/postgres/data
RUN su postgres -c "initdb -D /var/lib/postgres/data -E UTF-8"
-RUN cp /usr/share/pgsql/postgresql.conf.sample /var/lib/postgres/data/postgresql.conf
\ No newline at end of file
+RUN cp /usr/share/pgsql/postgresql.conf.sample /var/lib/postgres/data/postgresql.conf
+
+RUN echo "PS1=\"[\u@nightly-tests] # \"" > ~/.bashrc
diff --git a/setup/package.dfdebian b/setup/package.dfdebian
index 8afcad6431b..f59233e4db4 100644
--- a/setup/package.dfdebian
+++ b/setup/package.dfdebian
@@ -68,3 +68,5 @@ RUN apt-get update -qq && \
COPY requirements.txt /opt/release/requirements.txt
RUN pip install -r /opt/release/requirements.txt
+
+RUN echo "PS1=\"[\u@nightly-tests] # \"" > ~/.bashrc
diff --git a/setup/package.py b/setup/package.py
index 7de6b48fba8..ce0db21bcf9 100755
--- a/setup/package.py
+++ b/setup/package.py
@@ -120,7 +120,7 @@ class OdooDocker(object):
def __init__(self):
self.log_file = NamedTemporaryFile(mode='w+b', prefix="bash", suffix=".txt", delete=False)
self.port = 8069 # TODO sle: reliable way to get a free port?
- self.prompt_re = '(\r\nroot@|bash-).*# '
+ self.prompt_re = '\[root@nightly-tests\] #'
self.timeout = 600
def system(self, command):