[MERGE] forward port of branch 8.0 up to 592f1cd

This commit is contained in:
Christophe Simonis
2015-01-21 12:11:38 +01:00
23 changed files with 275 additions and 258 deletions
+4 -2
View File
@@ -1,4 +1,7 @@
[![Build Status](http://runbot.odoo.com/runbot/badge/default/1/master.svg)](http://runbot.odoo.com/runbot)
[![Build Status](http://runbot.odoo.com/runbot/badge/flat/1/master.svg)](http://runbot.odoo.com/runbot)
[![Tech Doc](http://img.shields.io/badge/master-docs-8f8f8f.svg?style=flat)](http://www.odoo.com/documentation/master)
[![Help](http://img.shields.io/badge/master-help-8f8f8f.svg?style=flat)](https://www.odoo.com/forum/help-1)
[![Nightly Builds](http://img.shields.io/badge/master-nightly-8f8f8f.svg?style=flat)](http://nightly.odoo.com/)
Odoo
----
@@ -13,7 +16,6 @@ a full-featured <a href="https://www.odoo.com">Open Source ERP</a> when you inst
Getting started with Odoo
-------------------------
For a standard installation please follow the <a href="https://www.odoo.com/documentation/8.0/setup/install.html">Setup instructions</a>
from the documentation.
@@ -27,43 +27,22 @@ class asset_modify(osv.osv_memory):
_name = 'asset.modify'
_description = 'Modify Asset'
def _get_asset_method_time(self, cr, uid, ids, field_name, arg, context=None):
if ids and len(ids) == 1 and context.get('active_id'):
asset = self.pool['account.asset.asset'].browse(cr, uid, context.get('active_id'), context=context)
return {ids[0]: asset.method_time}
else:
return dict.fromkeys(ids, False)
_columns = {
'name': fields.char('Reason', required=True),
'method_number': fields.integer('Number of Depreciations', required=True),
'method_period': fields.integer('Period Length'),
'method_end': fields.date('Ending date'),
'note': fields.text('Notes'),
'asset_method_time': fields.function(_get_asset_method_time, type='char', string='Asset Method Time', readonly=True),
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
""" Returns views and fields for current model.
@param cr: A database cursor
@param user: ID of the user currently logged in
@param view_id: list of fields, which required to read signatures
@param view_type: defines a view type. it can be one of (form, tree, graph, calender, gantt, search, mdx)
@param context: context arguments, like lang, time zone
@param toolbar: contains a list of reports, wizards, and links related to current model
@return: Returns a dictionary that contains definition for fields, views, and toolbars
"""
if not context:
context = {}
asset_obj = self.pool.get('account.asset.asset')
result = super(asset_modify, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
asset_id = context.get('active_id', False)
active_model = context.get('active_model', '')
if active_model == 'account.asset.asset' and asset_id:
asset = asset_obj.browse(cr, uid, asset_id, context=context)
doc = etree.XML(result['arch'])
if asset.method_time == 'number':
node = doc.xpath("//field[@name='method_end']")[0]
node.set('invisible', '1')
elif asset.method_time == 'end':
node = doc.xpath("//field[@name='method_number']")[0]
node.set('invisible', '1')
result['arch'] = etree.tostring(doc)
return result
def default_get(self, cr, uid, fields, context=None):
""" To get default values for the object.
@param self: The object pointer.
@@ -87,6 +66,8 @@ class asset_modify(osv.osv_memory):
res.update({'method_period': asset.method_period})
if 'method_end' in fields and asset.method_time == 'end':
res.update({'method_end': asset.method_end})
if context.get('active_id'):
res['asset_method_time'] = self._get_asset_method_time(cr, uid, [0], 'asset_method_time', [], context=context)[0]
return res
def modify(self, cr, uid, ids, context=None):
@@ -7,13 +7,14 @@
<field name="model">asset.modify</field>
<field name="arch" type="xml">
<form string="Modify Asset">
<field name="asset_method_time" invisible="1"/>
<group string="Asset Durations to Modify" col="4">
<group colspan="2" col="2">
<field name="name"/>
<field name="method_number"/>
<field name="method_number" attrs="{'invisible': [('asset_method_time', '=', 'end')]}"/>
</group>
<group colspan="2" col="2">
<field name="method_end"/>
<field name="method_end" attrs="{'invisible': [('asset_method_time', '=', 'number')]}"/>
<label for="method_period"/>
<div>
<field name="method_period" class="oe_inline"/> months
+1 -1
View File
@@ -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})
+1 -1
View File
@@ -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
+16 -11
View File
@@ -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)
+31 -27
View File
@@ -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
+2 -2
View File
@@ -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):
+13 -2
View File
@@ -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
+3 -6
View File
@@ -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();
+3
View File
@@ -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 () {
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
10 access_product_product_price_type_public product.price.type.public product.model_product_price_type 1 0 0 0
11 access_product_style product.style.public website_sale.model_product_style 1 0 0 0
12 access_product_supplierinfo product.supplierinfo.public product.model_product_supplierinfo 1 0 0 0
13 access_product_attribute_public product.attribute public product.model_product_attribute base.group_public 1 0 0 0
14 access_product_attribute_value_public product.attribute value public product.model_product_attribute_value base.group_public 1 0 0 0
15 access_product_attribute_price_public product.attribute price public product.model_product_attribute_price base.group_public 1 0 0 0
16 access_product_attribute_line_public product.attribute line public product.model_product_attribute_line base.group_public 1 0 0 0
+1 -3
View File
@@ -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)
+5 -6
View File
@@ -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):
+43 -60
View File
@@ -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
+94 -73
View File
@@ -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):
+9 -17
View File
@@ -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()
+24 -4
View File
@@ -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
-4
View File
@@ -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)
+1 -1
View File
@@ -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))
+4 -2
View File
@@ -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
RUN cp /usr/share/pgsql/postgresql.conf.sample /var/lib/postgres/data/postgresql.conf
RUN echo "PS1=\"[\u@nightly-tests] # \"" > ~/.bashrc
+2
View File
@@ -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
+1 -1
View File
@@ -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):