[MERGE] forward port of branch 9.0 up to e37ba61

This commit is contained in:
Christophe Simonis
2016-06-20 14:28:20 +02:00
19 changed files with 151 additions and 19 deletions
+3
View File
@@ -709,6 +709,9 @@ class AccountInvoice(models.Model):
line2[tmp]['credit'] = (am < 0) and -am or 0.0
line2[tmp]['amount_currency'] += l['amount_currency']
line2[tmp]['analytic_line_ids'] += l['analytic_line_ids']
qty = l.get('quantity')
if qty:
line2[tmp]['quantity'] = line2[tmp].get('quantity', 0.0) + qty
else:
line2[tmp] = l
line = []
+2 -1
View File
@@ -51,7 +51,8 @@
<field name="price_unit" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="price_subtotal" invisible="1" widget="monetary"/>
<field name="price_subtotal_incl" invisible="1" widget="monetary"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags"/>
<field name="tax_ids" invisible="1"/>
<field name="notice"/>
</group>
</form>
+2 -2
View File
@@ -167,8 +167,8 @@ class pos_details(report_sxw.rml_parse):
for order in pos_order_obj.browse(self.cr, self.uid, pos_ids):
currency = order.session_id.currency_id
for line in order.lines:
if line.product_id.taxes_id:
line_taxes = line.product_id.taxes_id.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
if line.tax_ids_after_fiscal_position:
line_taxes = line.tax_ids_after_fiscal_position.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
for tax in line_taxes['taxes']:
taxes.setdefault(tax['id'], {'name': tax['name'], 'amount':0.0})
taxes[tax['id']]['amount'] += tax['amount']
+3 -2
View File
@@ -715,8 +715,9 @@ class product_template(osv.osv):
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
template = self.browse(cr, uid, id, context=context)
default['name'] = _("%s (copy)") % (template['name'])
if 'name' not in default:
template = self.browse(cr, uid, id, context=context)
default['name'] = _("%s (copy)") % (template['name'])
return super(product_template, self).copy(cr, uid, id, default=default, context=context)
_defaults = {
+1 -1
View File
@@ -241,7 +241,7 @@ with directly by external objects or search view controls
.. js:class:: openerp.web.search.Facet
A `backbone model`_ representing a single facet of the current
research. May map to a search field, or to a more complex or
search. May map to a search field, or to a more complex or
fuzzier input (e.g. a custom filter or an advanced search).
.. js:attribute:: category
+17
View File
@@ -0,0 +1,17 @@
Austria, 2016-06-16
Seekda GmbH agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Holger Lausen holger.lausen@seekda.com https://github.com/holgerlausen
List of contributors:
Holger Lausen holger.lausen@seekda.com https://github.com/holgerlausen
Florian Kisser florian.kisser@seekda.com https://github.com/floriankisser
+9
View File
@@ -0,0 +1,9 @@
Nicaragua, Jun 16th, 2016
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
William Moreno williamjmorenor@gmail.com https://github.com/williamjmorenor
+1 -1
View File
@@ -641,7 +641,7 @@ Model Reference
.. automethod:: read
.. automethod:: read_group
.. rubric:: Research
.. rubric:: Searching
.. automethod:: search
.. automethod:: search_count
+1 -1
View File
@@ -815,7 +815,7 @@ attributes:
``default_group_by``
whether the kanban view should be grouped if no grouping is specified via
the action or the current research. Should be the name of the field to group
the action or the current search. Should be the name of the field to group
by when no grouping is otherwise specified
``default_order``
cards sorting order used if the user has not already sorted the records (via
+1 -1
View File
@@ -106,7 +106,7 @@
</group>
<group string="Advanced Properties">
<field name="serialization_field_id"
domain="[('ttype','=','serialized'), ('model_id', '=', model_id)]"
domain="[('ttype','=','serialized'), ('model_id', '=', parent.model)]"
attrs="{'readonly': [('state','=','base')]}"/>
<field name="related"/>
<field name="depends"/>
+1 -6
View File
@@ -439,9 +439,8 @@ class QWeb(orm.AbstractModel):
d = qwebcontext.copy()
if 'lang' in template_attributes:
init_lang = d.context.get('lang', 'en_US')
lang = template_attributes['lang']
d.context['lang'] = self.eval(lang, d) or lang
d.context = dict(d.context, lang=self.eval(lang, d) or lang)
if not self.pool['res.lang'].search(d.cr, d.uid, [('code', '=', d.context['lang'])], count=True, context=d.context):
d.context['lang'] = qwebcontext.get('res_company') and qwebcontext.get('res_company').partner_id.lang or 'en_US'
_logger.info("'%s' is not a valid language code, is an empty field or is not installed, falling back to %s" % (lang, d.context['lang']))
@@ -459,10 +458,6 @@ class QWeb(orm.AbstractModel):
d['generated_attributes'] = generated_attributes
res = self.render(cr, uid, template, d)
# we need to reset the lang after the rendering
if 'lang' in template_attributes:
d.context['lang'] = init_lang
return res
def render_tag_call_assets(self, element, template_attributes, generated_attributes, qwebcontext):
@@ -4,6 +4,7 @@ access_test_new_api_bravo,access_test_new_api_bravo,model_test_new_api_bravo,,1,
access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1
access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1
access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1
access_emailmessage,test_new_api_emailmessage,test_new_api.model_test_new_api_emailmessage,,1,1,1,1
access_multi,test_new_api_multi,test_new_api.model_test_new_api_multi,,1,1,1,1
access_multi_line,test_new_api_multi_line,test_new_api.model_test_new_api_multi_line,,1,1,1,1
access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_category test_new_api_category test_new_api.model_test_new_api_category 1 1 1 1
5 access_discussion test_new_api_discussion test_new_api.model_test_new_api_discussion 1 1 1 1
6 access_message test_new_api_message test_new_api.model_test_new_api_message 1 1 1 1
7 access_emailmessage test_new_api_emailmessage test_new_api.model_test_new_api_emailmessage 1 1 1 1
8 access_multi test_new_api_multi test_new_api.model_test_new_api_multi 1 1 1 1
9 access_multi_line test_new_api_multi_line test_new_api.model_test_new_api_multi_line 1 1 1 1
10 access_mixed test_new_api_mixed test_new_api.model_test_new_api_mixed 1 1 1 1
+11
View File
@@ -130,6 +130,9 @@ class Discussion(models.Model):
message_concat = fields.Text(string='Message concatenate')
important_messages = fields.One2many('test_new_api.message', 'discussion',
domain=[('important', '=', True)])
emails = fields.One2many('test_new_api.emailmessage', 'discussion')
important_emails = fields.One2many('test_new_api.emailmessage', 'discussion',
domain=[('important', '=', True)])
@api.onchange('moderator')
def _onchange_moderator(self):
@@ -210,6 +213,14 @@ class Message(models.Model):
return [('author.partner_id', operator, value)]
class EmailMessage(models.Model):
_name = 'test_new_api.emailmessage'
_inherits = {'test_new_api.message': 'message'}
message = fields.Many2one('test_new_api.message', 'Message',
required=True, ondelete='cascade')
email_to = fields.Char('To')
class Multi(models.Model):
""" Model for testing multiple onchange methods in cascade that modify a
one2many field several times.
@@ -12,6 +12,7 @@ class TestOnChange(common.TransactionCase):
super(TestOnChange, self).setUp()
self.Discussion = self.env['test_new_api.discussion']
self.Message = self.env['test_new_api.message']
self.EmailMessage = self.env['test_new_api.emailmessage']
def test_default_get(self):
""" checking values returned by default_get() """
@@ -223,3 +224,60 @@ class TestOnChange(common.TransactionCase):
self.assertIn('message_concat', result['value'])
self.assertEqual(result['value']['message_concat'],
"\n".join(["%s:%s" % (m.name, m.body) for m in discussion.messages]))
def test_onchange_one2many_with_domain_on_related_field(self):
""" test the value of the one2many field when defined with a domain on a related field"""
discussion = self.env.ref('test_new_api.discussion_0')
demo = self.env.ref('base.user_demo')
# mimic UI behaviour, so we get subfields
# (we need at least subfield: 'important_emails.important')
view_info = self.Discussion.fields_view_get(
view_id=self.env.ref('test_new_api.discussion_form').id,
view_type='form')
field_onchange = self.Discussion._onchange_spec(view_info=view_info)
self.assertEqual(field_onchange.get('messages'), '1')
BODY = "What a beautiful day!"
USER = self.env.user
# create standalone email
email = self.EmailMessage.create({
'discussion': discussion.id,
'name': "[%s] %s" % ('', USER.name),
'body': BODY,
'author': USER.id,
'important': False,
})
# check if server-side cache is working correctly
self.env.invalidate_all()
self.assertIn(email, discussion.emails)
self.assertNotIn(email, discussion.important_emails)
email.important = True
self.assertIn(email, discussion.important_emails)
# check that when trigger an onchange, we don't reset important emails
# (force `invalidate_all` as but appear in onchange only when we get a
# cache miss)
self.env.invalidate_all()
self.assertEqual(len(discussion.messages), 4)
values = {
'name': "Foo Bar",
'moderator': demo.id,
'categories': [(4, cat.id) for cat in discussion.categories],
'messages': [(4, msg.id) for msg in discussion.messages],
'participants': [(4, usr.id) for usr in discussion.participants],
'message_changes': 0,
'important_messages': [(4, msg.id) for msg in discussion.important_messages],
'important_emails': [(4, eml.id) for eml in discussion.important_emails],
}
result = discussion.onchange(values, 'name', field_onchange)
# When one2many domain contains non-computed field, things are ok
self.assertEqual(result['value']['important_messages'],
[(1, email.message.id, {'name': u'[Foo Bar] %s' % USER.name})])
# But here with commit 5676d81, we get value of: [(2, email.id)]
self.assertEqual(result['value']['important_emails'],
[(1, email.id, {'name': u'[Foo Bar] %s' % USER.name})])
+23
View File
@@ -59,12 +59,35 @@
<tree string="Messages">
<field name="name"/>
<field name="body"/>
<field name="important"/>
</tree>
<form string="Message" version="7.0">
<group>
<field name="name"/>
<field name="author"/>
<field name="size"/>
<field name="important"/>
</group>
<label for="body"/>
<field name="body"/>
</form>
</field>
</page>
<page string="Emails">
<field name="important_emails">
<tree name="Important Messages">
<field name="name"/>
<field name="body"/>
<field name="important"/>
<field name="email_to"/>
</tree>
<form string="Important Message" version="7.0">
<group>
<field name="name"/>
<field name="author"/>
<field name="size"/>
<field name="important"/>
<field name="email_to"/>
</group>
<label for="body"/>
<field name="body"/>
+7
View File
@@ -1014,6 +1014,13 @@ class Field(object):
# ``records``, except fields currently being computed
spec = []
for field, path in records._field_triggers[self]:
if not field.compute:
# Note: do not invalidate non-computed fields. Such fields may
# require invalidation in general (like *2many fields with
# domains) but should not be invalidated in this case, because
# we would simply lose their values during an onchange!
continue
target = env[field.model_name]
computed = target.browse(env.computed[field])
if path == 'id' and field.model_name == records._name:
+1 -1
View File
@@ -2596,8 +2596,8 @@ class BaseModel(object):
self._set_default_value_on_column(cr, k, context=context)
# add the NOT NULL constraint
try:
cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
cr.commit()
cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
_schema.debug("Table '%s': column '%s': added NOT NULL constraint",
self._table, k)
except Exception:
+8 -2
View File
@@ -961,7 +961,10 @@ class expression(object):
if right is not False:
if isinstance(right, basestring):
op = {'!=': '=', 'not like': 'like', 'not ilike': 'ilike'}.get(operator, operator)
ids2 = [x[0] for x in comodel.name_search(cr, uid, right, [], op, context=context, limit=None)]
domain = column._domain
if callable(domain):
domain = domain(model)
ids2 = [x[0] for x in comodel.name_search(cr, uid, right, domain or [], op, context=context, limit=None)]
if ids2:
operator = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
elif isinstance(right, collections.Iterable):
@@ -1012,7 +1015,10 @@ class expression(object):
if right is not False:
if isinstance(right, basestring):
op = {'!=': '=', 'not like': 'like', 'not ilike': 'ilike'}.get(operator, operator)
res_ids = [x[0] for x in comodel.name_search(cr, uid, right, [], op, context=context, limit=None)]
domain = column._domain
if callable(domain):
domain = domain(model)
res_ids = [x[0] for x in comodel.name_search(cr, uid, right, domain or [], op, context=context, limit=None)]
if res_ids:
operator = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
else:
+1 -1
View File
@@ -77,7 +77,7 @@ class configmanager(object):
# Not exposed in the configuration file.
self.blacklist_for_save = set([
'publisher_warranty_url', 'load_language', 'root_path',
'init', 'save', 'config', 'update', 'stop_after_init'
'init', 'save', 'config', 'update', 'stop_after_init', 'dev_mode'
])
# dictionary mapping option destination (keys in self.options) to MyOptions.