[MERGE] forward port of branch 8.0 up to 592f1cd
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
[](http://runbot.odoo.com/runbot)
|
||||
[](http://runbot.odoo.com/runbot)
|
||||
[](http://www.odoo.com/documentation/master)
|
||||
[](https://www.odoo.com/forum/help-1)
|
||||
[](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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user