[MERGE] forward port of branch saas-6 up to e9f1ee4

This commit is contained in:
Christophe Simonis
2015-10-01 15:16:59 +02:00
38 changed files with 345 additions and 80 deletions
+2 -2
View File
@@ -60,8 +60,8 @@
<th>Description</th>
<th class="text-right">Quantity</th>
<th class="text-right">Unit Price</th>
<th t-if="display_discount" groups="sale.group_discount_per_so_line">Disc.(%)</th>
<th>Taxes</th>
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</th>
<th class="text-right">Taxes</th>
<th class="text-right">Price</th>
</tr>
</thead>
@@ -4,6 +4,8 @@
from collections import defaultdict
from datetime import datetime
from dateutil.relativedelta import relativedelta
import datetime as DT
import dateutil
import time
import logging
@@ -121,7 +123,10 @@ class base_action_rule(osv.osv):
""" Prepare the context used when evaluating python code
:returns: dict -- evaluation context given to (safe_)eval """
return {
'datetime': DT,
'dateutil': dateutil,
'time': time,
'uid': uid,
'user': self.pool['res.users'].browse(cr, uid, uid, context=context),
}
@@ -7,7 +7,7 @@ from os.path import join as opj
import openerp
from openerp import fields
from openerp.osv import osv
from openerp.tools import convert_file
from openerp.tools import convert_file, exception_to_unicode
from openerp.tools.translate import _
from openerp.exceptions import UserError
@@ -105,7 +105,7 @@ class ir_module_module(osv.osv):
self.import_module(cr, uid, mod_name, path, force=force, context=context)
success.append(mod_name)
except Exception, e:
errors[mod_name] = str(e)
errors[mod_name] = exception_to_unicode(e)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import test_new_lead_notification
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import unittest
from openerp.tests.common import TransactionCase
class NewLeadNotificationTest(TransactionCase):
@unittest.skip("Broken in 9.0. auto_subscribe does not handle channels")
def test_new_lead_notification(self):
# Create a new user
groups = [
self.env.ref('base.group_user').id,
self.env.ref('base.group_sale_salesman').id,
]
user = self.env["res.users"].create({
"name": __file__,
"login": __file__,
'groups_id': [(6, 0, groups)],
})
chan = self.env['mail.channel'].create({
'name': 'Follow leads',
'group_ids': [(6, 0, groups)],
})
# Subscribe him to sales department
team = self.env.ref("sales_team.team_sales_department")
subtype = self.env.ref("crm.mt_salesteam_lead")
team.message_subscribe(channel_ids=[chan.id], subtype_ids=[subtype.id])
# Imitate what happens in the controller when somebody creates a new
# lead from the website form
lead = (self.env["crm.lead"]
.with_context(mail_create_nosubscribe=True)
.sudo()
.create({"contact_name": "Somebody",
"description": "Some question",
"email_from": "somemail@example.com",
"name": "Some subject",
"partner_name": "Some company",
"team_id": team.id,
"phone": "+0000000000"}))
# The user should have a new unread message
self.assertTrue(lead.sudo(user).message_unread)
+1 -1
View File
@@ -12,7 +12,7 @@ from urlparse import urlparse
from openerp import models, fields, api, _
URL_REGEX = r'(\bhref=[\'"]([^\'"]+)[\'"])'
URL_REGEX = r'(\bhref=[\'"](?!mailto:)([^\'"]+)[\'"])'
def VALIDATE_URL(url):
if urlparse(url).scheme not in ('http', 'https', 'ftp', 'ftps'):
+8
View File
@@ -5,6 +5,8 @@ import logging
from email.utils import formataddr
from urlparse import urljoin
import psycopg2
from openerp import _, api, fields, models
from openerp import tools
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
@@ -285,6 +287,12 @@ class MailMail(models.Model):
'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option',
mail.id, mail.message_id)
raise
except psycopg2.Error:
# If an error with the database occurs, chances are that the cursor is unusable.
# This will lead to an `psycopg2.InternalError` being raised when trying to write
# `state`, shadowing the original exception and forbid a retry on concurrent
# update. Let's bubble it.
raise
except Exception as e:
failure_reason = tools.ustr(e)
_logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason)
+7 -7
View File
@@ -201,6 +201,13 @@ class MailThread(models.AbstractModel):
doc_name = self.env['ir.model'].search([('model', '=', self._name)]).read(['name'])[0]['name']
thread.message_post(body=_('%s created') % doc_name)
# auto_subscribe: take values and defaults into account
create_values = dict(values)
for key, val in self._context.iteritems():
if key.startswith('default_') and key[8:] not in create_values:
create_values[key[8:]] = val
thread.message_auto_subscribe(create_values.keys(), values=create_values)
# track values
if not self._context.get('mail_notrack'):
if 'lang' not in self._context:
@@ -212,13 +219,6 @@ class MailThread(models.AbstractModel):
initial_values = {thread.id: dict.fromkeys(tracked_fields, False)}
track_thread.message_track(tracked_fields, initial_values)
# auto_subscribe: take values and defaults into account
create_values = dict(values)
for key, val in self._context.iteritems():
if key.startswith('default_') and key[8:] not in create_values:
create_values[key[8:]] = val
thread.message_auto_subscribe(create_values.keys(), values=create_values)
return thread
@api.multi
+5
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
import urlparse
import re
import werkzeug.urls
@@ -79,6 +80,10 @@ class MailMail(osv.Model):
domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "web.base.url", context=context)
base = "<base href='%s'>" % domain
body = tools.append_content_to_html(base, body, plaintext=False, container_tag='div')
# resolve relative image url to absolute for outlook.com
def _sub_relative2absolute(match):
return match.group(1) + urlparse.urljoin(domain, match.group(2)) + '"'
body = re.sub('(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)"', _sub_relative2absolute, body)
# generate tracking URL
if mail.statistics_ids:
+12 -9
View File
@@ -46,18 +46,21 @@ class product_uom(osv.osv):
def name_create(self, cr, uid, name, context=None):
""" The UoM category and factor are required, so we'll have to add temporary values
for imported UoMs """
if not context:
context = {}
uom_categ = self.pool.get('product.uom.categ')
values = {self._rec_name: name, 'factor': 1}
# look for the category based on the english name, i.e. no context on purpose!
# TODO: should find a way to have it translated but not created until actually used
categ_misc = 'Unsorted/Imported Units'
categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
if categ_id:
categ_id = categ_id[0]
else:
categ_id, _ = uom_categ.name_create(cr, uid, categ_misc)
uom_id = self.create(cr, uid, {self._rec_name: name,
'category_id': categ_id,
'factor': 1})
if not context.get('default_category_id'):
categ_misc = 'Unsorted/Imported Units'
categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
if categ_id:
values['category_id'] = categ_id[0]
else:
values['category_id'] = uom_categ.name_create(
cr, uid, categ_misc, context=context)[0]
uom_id = self.create(cr, uid, values, context=context)
return self.name_get(cr, uid, [uom_id], context=context)[0]
def create(self, cr, uid, data, context=None):
@@ -127,5 +127,12 @@
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record model="ir.rule" id="report_project_task_user_report_comp_rule">
<field name="name">Task Analysis multi-company</field>
<field name="model_id" ref="model_report_project_task_user"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</openerp>
+1 -1
View File
@@ -71,7 +71,7 @@ class PurchaseOrder(models.Model):
@api.model
def _default_picking_type(self):
type_obj = self.env['stock.picking.type']
company_id = self.env.user.company_id.id
company_id = self.env.context.get('company_id') or self.env.user.company_id.id
types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)])
if not types:
types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id', '=', False)])
+2 -1
View File
@@ -18,6 +18,7 @@ from report_helper import WebKitHelper
import openerp
from openerp.modules.module import get_module_resource
from openerp.report.report_sxw import *
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.tools.translate import _
from urllib import urlencode, quote as quote
@@ -102,7 +103,7 @@ class WebKitParser(report_sxw):
def get_lib(self, cursor, uid):
"""Return the lib wkhtml path"""
proxy = self.pool['ir.config_parameter']
webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
webkit_path = proxy.get_param(cursor, SUPERUSER_ID, 'webkit_path')
if not webkit_path:
try:
+1 -1
View File
@@ -29,7 +29,7 @@ var GraphView = View.extend({
this.widget = undefined;
},
start: function () {
var load_fields = this.model.call('fields_get', [])
var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()})
.then(this.prepare_fields.bind(this));
return $.when(this._super(), load_fields);
+1 -1
View File
@@ -73,7 +73,7 @@ var PivotView = View.extend({
start: function () {
this.$table_container = this.$('.o-pivot-table');
var load_fields = this.model.call('fields_get', [])
var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()})
.then(this.prepare_fields.bind(this));
return $.when(this._super(), load_fields).then(this.render_field_selection.bind(this));
+1 -1
View File
@@ -163,7 +163,7 @@ class ir_http(orm.AbstractModel):
if path[1] == request.website.default_lang_code:
request.context['edit_translations'] = False
if not request.context.get('tz'):
request.context['tz'] = request.session['geoip'].get('time_zone')
request.context['tz'] = request.session.get('geoip', {}).get('time_zone')
# bind modified context
request.website = request.website.with_context(request.context)
+5 -2
View File
@@ -120,7 +120,7 @@ hr {
font-size: 120%;
}
.jumbotron.para_large p{
.jumbotron.para_large p {
font-size: 150%;
}
@@ -184,7 +184,6 @@ body > nav.navbar {
}
}
/* ----- BOOTSTRAP FIX ----- */
@-moz-document url-prefix() {
.table .img-responsive {
width: 100%;
@@ -753,6 +752,10 @@ div.carousel .item.text_only .container > .carousel-image {
margin-right: 0 !important;
}
/* ---- footer style ---- {{{ */
#footer div.row {
word-wrap: break-word;
}
.container nav#oe_main_menu_navbar {
position: inherit;
}
+6 -3
View File
@@ -90,9 +90,8 @@ hr
.para_large
font-size: 120%
.jumbotron.para_large p{
font-size: 150%;
}
.jumbotron.para_large p
font-size: 150%
.readable
font-size: 120%
@@ -616,6 +615,10 @@ div.carousel
.navbar-nav.navbar-right:last-child
margin-right: 0 !important
/* ---- footer style ---- {{{ */
#footer div.row
word-wrap: break-word
.container
nav#oe_main_menu_navbar
position: inherit
+3 -3
View File
@@ -382,13 +382,13 @@
<xpath expr="//div[@id='footer']" position="replace">
<div id="footer" class="container hidden-print">
<div class="row">
<div class="col-md-3">
<div class="col-md-4 col-lg-3">
<h4>Our products &amp; Services</h4>
<ul class="list-unstyled" id="products">
<li><a href="/">Home</a></li>
</ul>
</div>
<div class="col-md-3" id="info">
<div class="col-md-4 col-lg-3" id="info">
<h4>Connect with us</h4>
<ul class="list-unstyled">
<li><a href="/page/website.contactus">Contact us</a></li>
@@ -406,7 +406,7 @@
<a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
</h2>
</div>
<div class="col-md-5 col-lg-offset-1">
<div class="col-md-4 col-lg-5 col-lg-offset-1">
<h4>
<span t-field="res_company.name" />
<small> - <a href="/page/website.aboutus">About us</a></small>
+7 -1
View File
@@ -217,7 +217,9 @@ class sale_order(osv.osv):
options = []
for option in quote_template.options:
if pricelist_id:
price = pricelist_obj.price_get(cr, uid, [pricelist_id], option.product_id.id, 1, context=context)[pricelist_id]
uom_context = context.copy()
uom_context['uom'] = option.uom_id.id
price = pricelist_obj.price_get(cr, uid, [pricelist_id], option.product_id.id, 1, context=uom_context)[pricelist_id]
else:
price = option.price_unit
options.append((0, 0, {
@@ -405,6 +407,10 @@ class sale_order_option(osv.osv):
if product.description_sale:
self.name += '\n' + product.description_sale
self.uom_id = product.product_tmpl_id.uom_id
if product and self.order_id.pricelist_id:
partner_id = self.order_id.partner_id.id
pricelist = self.order_id.pricelist_id.id
self.price_unit = self.order_id.pricelist_id.price_get(product.id, self.quantity, partner_id)[pricelist]
class product_template(osv.Model):
+6 -3
View File
@@ -803,13 +803,16 @@ class website_sale(http.Controller):
# find an already existing transaction
tx = request.website.sale_get_transaction()
if tx:
if tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx_id = tx.id
if tx.reference != order.name:
tx = False
tx_id = False
elif tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx.write({
'acquirer_id': acquirer_id,
'amount': order.amount_total,
})
tx_id = tx.id
else:
if not tx:
tx_id = transaction_obj.create(cr, SUPERUSER_ID, {
'acquirer_id': acquirer_id,
'type': 'form',
@@ -93,9 +93,9 @@ $('.oe_website_sale').each(function () {
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
var product_ids = [];
var product_dom = $("ul.js_add_cart_variants[data-attribute_value_ids]").first();
var product_dom = $(".js_add_cart_variants[data-attribute_value_ids]").first();
product_dom.data("attribute_value_ids").forEach(function(entry) {
product_ids.push(entry[0]);});
product_ids.push(entry);});
var qty = $(event.target).closest('form').find('input[name="add_qty"]').val();
if ($("#product_detail").length) {
@@ -241,7 +241,7 @@ $('.oe_website_sale').each(function () {
});
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
var $ul = $(ev.target).closest('ul.js_add_cart_variants');
var $ul = $(ev.target).closest('.js_add_cart_variants');
var $parent = $ul.closest('.js_product');
var $product_id = $parent.find('input.product_id').first();
var $price = $parent.find(".oe_price:first .oe_currency_value");
@@ -312,7 +312,7 @@ $('.oe_website_sale').each(function () {
$('input.js_product_change', this).first().trigger('change');
});
$('ul.js_add_cart_variants', oe_website_sale).each(function () {
$('.js_add_cart_variants', oe_website_sale).each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
+1 -1
View File
@@ -366,7 +366,7 @@
<h1 itemprop="name" t-field="product.name">Product Name</h1>
<span itemprop="url" style="display:none;" t-esc="'/shop/product/%s' % slug(product)"/>
<form t-att-action="keep('/shop/cart/update')" class="js_add_cart_variants" method="POST">
<form t-att-action="keep('/shop/cart/update')" class="js_add_cart_variants" t-att-data-attribute_value_ids="product.product_variant_ids.ids" method="POST">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="js_product">
<t t-placeholder="select">
@@ -6,8 +6,8 @@ var website = require('website.website');
var base = require('web_editor.base');
require('website_sale.website_sale');
if(!$('ul.js_add_cart_variants[data-attribute_value_ids]').length) {
return $.Deferred().reject("DOM doesn't contain 'ul.js_add_cart_variants[data-attribute_value_ids]'");
if(!$('.js_add_cart_variants[data-attribute_value_ids]').length) {
return $.Deferred().reject("DOM doesn't contain '.js_add_cart_variants[data-attribute_value_ids]'");
}
$('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
@@ -71,7 +71,7 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
});
$modal.find('input[name="add_qty"]').val(quantity).change();
$('ul.js_add_cart_variants').each(function () {
$('.js_add_cart_variants').each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
@@ -82,7 +82,7 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
var qty = $dom.find('input[name="add_qty"]').val();
var product_ids = [product_id];
var $products_dom = [];
$modal.find("ul.js_add_cart_variants[data-attribute_value_ids]").each(function(){
$modal.find(".js_add_cart_variants[data-attribute_value_ids]").each(function(){
var $el = $(this);
$products_dom.push($el);
_.each($el.data("attribute_value_ids"), function (values) {
@@ -152,7 +152,7 @@ var PDFSlidesViewer = (function(){
}
PDFSlidesViewer.prototype.toggleFullScreen = function(){
var el = this.canvas;
var el = this.canvas.parentNode;
var isFullscreenAvailable = document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled || false;
if(isFullscreenAvailable){ // Full screen supported
@@ -139,10 +139,10 @@ $(document).ready(function () {
// switching slide with keyboard
$(document).keydown(function (ev) {
if (ev.keyCode === 37) {
if (ev.keyCode === 37 || ev.keyCode === 38) {
embedded_viewer.previous();
}
if (ev.keyCode === 39) {
if (ev.keyCode === 39 || ev.keyCode === 40) {
embedded_viewer.next();
}
});
+15
View File
@@ -0,0 +1,15 @@
Spain, 2015-05-07
Grupo ESOC 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,
Nuria Pastor <n.pastor@grupoesoc.es> https://github.com/grupoesoc/
List of contributors:
Jairo Llopis <j.llopis@grupoesoc.es> https://github.com/Yajo
+11
View File
@@ -0,0 +1,11 @@
Spain, september 24th 2015
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,
Jairo Llopis yajo.sk8@gmail.com https://github.com/Yajo
+11
View File
@@ -0,0 +1,11 @@
Belgium, 2015-09-09
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,
Cédric Krier cedk@users.noreply.github.com https://github.com/cedk
+7 -7
View File
@@ -25,7 +25,7 @@ declaration.
Signed,
<name> <email> https://github.com/<login>
<name> <git_email> https://github.com/<login>
```
Replacing the following placeholders:
@@ -33,7 +33,7 @@ Signed,
* `<country>`: your country
* `<date>`: current date in the form `YYYY-MM-DD`
* `<name>`: your name
* `<email>`: your email
* `<git_email>`: your git committer email **(use `git config user.email` to see it)**
* `<login>`: your GitHub login
3. An Odoo R&D Team member will verify and accept your Pull Request. You can
@@ -61,11 +61,11 @@ declaration.
Signed,
<name> <email> https://github.com/<login>
<name> <git_email> https://github.com/<login>
List of contributors:
<name> <email> https://github.com/<login>
<name> <git_email> https://github.com/<login>
```
The List of contributors should list the individual contributors working
@@ -76,14 +76,14 @@ List of contributors:
* `<country>`: your country
* `<date>`: current date in the form `YYYY-MM-DD`
* `<name>`: your name
* `<email>`: your email
* `<git_email>`: git committer email **(use `git config user.email` to see it)**
* `<login>`: your GitHub login
3. An Odoo R&D Team member will verify and accept your Pull Request. You can
make other pull requests, but we won't be able to merge them until your CLA
signature is merged.
## If you dont have a github account
## If you don't have a github account
If you cannot submit your signature using a pull request, you may alternatively
print the CLA, complete it, sign it, scan it and send it by email to
@@ -95,7 +95,7 @@ make the pull request on your behalf.
# FAQ
## Why do i need to accept a CLA ?
## Why do I need to accept a CLA ?
The goal of having a Contributor License Agreement for Odoo is to:
+1 -1
View File
@@ -249,7 +249,7 @@ Common ORM methods
<reference/orm/oldapi>`::
>>> self.browse([7, 18, 12])
res.partner(7, 18, 12])
res.partner(7, 18, 12)
:meth:`~openerp.models.Model.exists`
Returns a new recordset containing only the records which exist in the
+6 -13
View File
@@ -139,21 +139,14 @@ class view(osv.osv):
result.update(map(itemgetter('res_id', 'id'), data_ids))
return result
def _convert(self, cr, uid, s, view):
matches = re.finditer('[^%]%\((.*?)\)[ds]', s)
done = []
for m in matches:
found = m.group()[1:]
if found in done:
continue
done.append(found)
xmlid = m.groups()[0]
def _resolve_external_ids(self, cr, uid, view, arch_fs):
def replacer(m):
xmlid = m.group('xmlid')
if '.' not in xmlid:
mod = view.get_external_id(cr, uid).get(view.id).split('.')[0]
xmlid = '%s.%s' % (mod, xmlid)
res = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, xmlid)
s = s.replace(found, str(res))
return s
return m.group('prefix') + str(self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, xmlid))
return re.sub('(?P<prefix>[^%])%\((?P<xmlid>.*?)\)[ds]', replacer, arch_fs)
def _arch_get(self, cr, uid, ids, name, arg, context=None):
result = {}
@@ -164,7 +157,7 @@ class view(osv.osv):
fullpath = get_resource_path(*view.arch_fs.split('/'))
arch_fs = get_view_arch_from_file(fullpath, view.xml_id)
# replace %(xml_id)s, %(xml_id)d, %%(xml_id)s, %%(xml_id)d by the res_id
arch_fs = self._convert(cr, uid, arch_fs, view)
arch_fs = arch_fs and self._resolve_external_ids(cr, uid, view, arch_fs)
result[view.id] = arch_fs or view.arch_db
return result
+4
View File
@@ -11,4 +11,8 @@
'data': ['ir.model.access.csv'],
'installable': True,
'auto_install': False,
'test': [
'tests/test_import_reference.yml',
'tests/test_import_menuitem.yml',
]
}
@@ -0,0 +1,67 @@
- |
YAML Import menuitem scenario:
Check that !menuitem import works with YAML.
- |
Given a standard three level menuitem structure including:
A top level menu item (no parent no action)
-
!menuitem {
id: test_menu_top,
name: "Test Menu Top",
}
- >
An intermediary menu item (parent, but no action)
-
!menuitem {
id: test_menu_sub,
parent: test_menu_top,
name: "Test Menu Sub",
}
- >
A leaf menu item (parent and action)
-
!menuitem {
id: test_menu_action,
name: "Test Menu Action",
parent: test_menu_sub,
action: base.open_module_tree,
}
- >
Another leaf menu item (parent and action) in another menu structure with
dotted parent id
-
!menuitem {
id: test_menu_action_2,
name: "Test Menu Action 2",
parent: base.menu_management,
action: base.open_module_tree,
sequence: 93,
}
- >
Then these menu items should be present and properly configured.
-
!assert { model: ir.ui.menu, id: test_menu_top, string: menu item "top" is properly configured }:
- name == 'Test Menu Top'
- parent_id.name == False
- action == False
-
!assert { model: ir.ui.menu, id: test_menu_sub, string: menu item "sub" is properly configured }:
- name == 'Test Menu Sub'
- parent_id.name == 'Test Menu Top'
- action == False
-
!assert { model: ir.ui.menu, id: test_menu_action, string: menu item "action" is properly configured }:
- name == 'Test Menu Action'
- parent_id.name == 'Test Menu Sub'
- action.name == 'Apps'
-
!assert { model: ir.ui.menu, id: test_menu_action_2, string: menu item "action_2" is properly configured }:
- name == 'Test Menu Action 2'
- sequence == 93
- parent_id.name == 'Apps'
- action.name == 'Apps'
@@ -0,0 +1,44 @@
- |
YAML Import reference scenario:
Check that importing into a "reference" type field works with YAML using
both implicit references and !ref.
- |
Given records imitating menu entries similar to the following entry from the base module:
<menuitem id="menu_module_tree" parent="menu_management" name="Apps"
sequence="5" action="open_module_tree" />
taken from openerp/addons/base/module/module_view.xml
- >
A Menu item using !record and implicit xml_id lookup and sequence cast:
-
!record { model: ir.ui.menu, id: test_menu_0 }:
name: "Local Modules 0"
parent_id: base.menu_management
sequence: 90
action: base.open_module_tree
- >
A Menu item using !record and explicit !ref and sequence !eval
-
!record { model: ir.ui.menu, id: test_menu_1 }:
name: "Local Modules 1"
parent_id: !refid base.menu_management
sequence: !eval 91
action: !refid base.open_module_tree
- >
Then these menu items should be present, properly configured and
pointing to the same action.
-
!assert { model: ir.ui.menu, id: test_menu_0, string: menu item 0 is properly configured }:
- name == 'Local Modules 0'
- sequence == 90
- parent_id.name == 'Apps'
- action.name == 'Apps'
-
!assert { model: ir.ui.menu, id: test_menu_1, string: menu item 1 is properly configured }:
- name == 'Local Modules 1'
- sequence == 91
- parent_id.name == 'Apps'
- action.name == 'Apps'
+26 -3
View File
@@ -5847,7 +5847,19 @@ class BaseModel(object):
if 'domain' in method_res:
result.setdefault('domain', {}).update(method_res['domain'])
if 'warning' in method_res:
result['warning'] = method_res['warning']
if result.get('warning'):
if method_res['warning']:
# Concatenate multiple warnings
warning = result['warning']
warning['message'] = '\n\n'.join(filter(None, [
warning.get('title'),
warning.get('message'),
method_res['warning'].get('title'),
method_res['warning'].get('message')
]))
warning['title'] = _('Warnings')
else:
result['warning'] = method_res['warning']
return
# onchange V7
@@ -5885,8 +5897,19 @@ class BaseModel(object):
if 'domain' in method_res:
result.setdefault('domain', {}).update(method_res['domain'])
if 'warning' in method_res:
result['warning'] = method_res['warning']
if result.get('warning'):
if method_res['warning']:
# Concatenate multiple warnings
warning = result['warning']
warning['message'] = '\n\n'.join(filter(None, [
warning.get('title'),
warning.get('message'),
method_res['warning'].get('title'),
method_res['warning'].get('message')
]))
warning['title'] = _('Warnings')
else:
result['warning'] = method_res['warning']
@api.multi
def onchange(self, values, field_name, field_onchange):
""" Perform an onchange on the given field.
+13 -6
View File
@@ -136,7 +136,7 @@ class YamlInterpreter(object):
id = xml_id
if '.' in xml_id:
module, id = xml_id.split('.', 1)
assert '.' not in id, "The ID reference '%s' must contains maximum one dot.\n" \
assert '.' not in id, "The ID reference '%s' must contain at most one dot.\n" \
"It is used to refer to other modules ID, in the form: module.record_id" \
% (xml_id,)
if module != self.module:
@@ -165,9 +165,9 @@ class YamlInterpreter(object):
_, id = self.pool['ir.model.data'].get_object_reference(self.cr, self.uid, module, checked_xml_id)
self.id_map[xml_id] = id
except ValueError:
raise ValueError("""%s not found when processing %s.
raise ValueError("""%r not found when processing %s.
This Yaml file appears to depend on missing data. This often happens for
tests that belong to a module's test suite and depend on each other.""" % (checked_xml_id, self.filename))
tests that belong to a module's test suite and depend on each other.""" % (xml_id, self.filename))
return id
@@ -337,7 +337,7 @@ class YamlInterpreter(object):
self.cr.commit()
def _create_record(self, model, fields, view_info=None, parent={}, default=True, context=None):
"""This function processes the !record tag in yalm files. It simulates the record creation through an xml
"""This function processes the !record tag in yaml files. It simulates the record creation through an xml
view (either specified on the !record tag or the default one for this object), including the calls to
on_change() functions, and sending only values for fields that aren't set as readonly.
:param model: model instance
@@ -526,7 +526,11 @@ class YamlInterpreter(object):
else:
value = ids
elif node.id:
value = self.get_id(node.id)
if field and field.type == 'reference':
record = self.get_record(node.id)
value = "%s,%s" % (record._name, record.id)
else:
value = self.get_id(node.id)
else:
value = None
return value
@@ -544,7 +548,7 @@ class YamlInterpreter(object):
elements = self.process_ref(expression, field)
if field.type in ("many2many", "one2many"):
value = [(6, 0, elements)]
else: # many2one
else: # many2one or reference
if isinstance(elements, (list,tuple)):
value = self._get_first_result(elements)
else:
@@ -565,6 +569,9 @@ class YamlInterpreter(object):
# enforce ISO format for string datetime values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
value = expression
elif field.type == "reference":
record = self.get_record(expression)
value = "%s,%s" % (record._name, record.id)
else: # scalar field
if is_eval(expression):
value = self.process_eval(expression)