[MERGE] forward port of branch 8.0 up to 017f346
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
<span t-field="line.description"/><br/>
|
||||
<span t-field="line.analytic_account.complete_name" t-if="line.analytic_account"/>
|
||||
<span t-field="line.analytic_account.complete_name"/>
|
||||
</td>
|
||||
<td style="text-center">
|
||||
<span t-field="line.ref"/>
|
||||
|
||||
@@ -293,7 +293,7 @@ class product_pricelist(osv.osv):
|
||||
if rule.base_pricelist_id:
|
||||
price_tmp = self._price_get_multi(cr, uid,
|
||||
rule.base_pricelist_id, [(product,
|
||||
qty, False)], context=context)[product.id]
|
||||
qty, partner)], context=context)[product.id]
|
||||
ptype_src = rule.base_pricelist_id.currency_id.id
|
||||
price_uom_id = qty_uom_id
|
||||
price = currency_obj.compute(cr, uid,
|
||||
|
||||
@@ -664,7 +664,7 @@ class product_template(osv.osv):
|
||||
temp_variants = []
|
||||
for variant in all_variants:
|
||||
for value_id in variant_id.value_ids:
|
||||
temp_variants.append(variant + [int(value_id)])
|
||||
temp_variants.append(sorted(variant + [int(value_id)]))
|
||||
if temp_variants:
|
||||
all_variants = temp_variants
|
||||
|
||||
@@ -682,7 +682,7 @@ class product_template(osv.osv):
|
||||
variants_active_ids = []
|
||||
variants_inactive = []
|
||||
for product_id in tmpl_id.product_variant_ids:
|
||||
variants = map(int,product_id.attribute_value_ids)
|
||||
variants = sorted(map(int,product_id.attribute_value_ids))
|
||||
if variants in all_variants:
|
||||
variants_active_ids.append(product_id.id)
|
||||
all_variants.pop(all_variants.index(variants))
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -92,21 +92,21 @@
|
||||
<td><strong>Total Without Taxes</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_untaxed"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Taxes</td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_tax"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_total"
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
|
||||
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -173,7 +173,7 @@ class purchase_requisition(osv.osv):
|
||||
'order_id': purchase_id,
|
||||
'product_id': product.id,
|
||||
'account_analytic_id': requisition_line.account_analytic_id.id,
|
||||
'taxes_id': [(6, 0, vals.get('taxes_ids', []))],
|
||||
'taxes_id': [(6, 0, vals.get('taxes_id', []))],
|
||||
})
|
||||
return vals
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<template id="style">
|
||||
body {
|
||||
color: #000 !important;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.border-black td {
|
||||
border-top: 1px solid black !important;
|
||||
|
||||
@@ -2831,7 +2831,8 @@ class stock_inventory_line(osv.osv):
|
||||
'prod_lot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"),
|
||||
'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True),
|
||||
'theoretical_qty': fields.function(_get_theoretical_qty, type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
store=True, readonly=True, string="Theoretical Quantity"),
|
||||
store={'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['location_id', 'product_id', 'package_id', 'product_uom_id', 'company_id', 'prod_lot_id', 'partner_id'], 20),},
|
||||
readonly=True, string="Theoretical Quantity"),
|
||||
'partner_id': fields.many2one('res.partner', 'Owner'),
|
||||
'product_name': fields.related('product_id', 'name', type='char', string='Product Name', store={
|
||||
'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
|
||||
|
||||
@@ -547,11 +547,13 @@ var py = {};
|
||||
};
|
||||
py.PY_call = function (callable, args, kwargs) {
|
||||
if (!args) {
|
||||
args = []; kwargs = {};
|
||||
} else if (typeof args === 'object' && !(args instanceof Array)) {
|
||||
args = [];
|
||||
}
|
||||
if (typeof args === 'object' && !(args instanceof Array)) {
|
||||
kwargs = args;
|
||||
args = [];
|
||||
} else if (!kwargs) {
|
||||
}
|
||||
if (!kwargs) {
|
||||
kwargs = {};
|
||||
}
|
||||
if (callable.__is_type) {
|
||||
|
||||
@@ -222,7 +222,6 @@
|
||||
['milliseconds', zero], ['minutes', zero], ['hours', zero],
|
||||
['weeks', zero]
|
||||
]);
|
||||
|
||||
var d = 0, s = 0, m = 0;
|
||||
var days = args.days.toJSON() + args.weeks.toJSON() * 7;
|
||||
var seconds = args.seconds.toJSON()
|
||||
@@ -496,13 +495,14 @@
|
||||
if (!py.PY_isInstance(other, datetime.timedelta)) {
|
||||
return py.NotImplemented;
|
||||
}
|
||||
|
||||
var s = tmxxx(this.year, this.month, this.day + other.days);
|
||||
return datetime.date.fromJSON(s.year, s.month, s.day);
|
||||
},
|
||||
__radd__: function (other) { return this.__add__(other); },
|
||||
__sub__: function (other) {
|
||||
if (py.PY_isInstance(other, datetime.timedelta)) {
|
||||
return this.__add__(other.__neg__());
|
||||
return py.PY_add(this, py.PY_negative(other));
|
||||
}
|
||||
if (py.PY_isInstance(other, datetime.date)) {
|
||||
// FIXME: getattr and sub API methods
|
||||
@@ -562,103 +562,182 @@
|
||||
return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
|
||||
});
|
||||
|
||||
var args = _.map(('year month day hour minute second microsecond '
|
||||
+ 'years months weeks days hours minutes secondes microseconds '
|
||||
+ 'weekday leakdays yearday nlyearday').split(' '), function (arg) {
|
||||
return [arg, null];
|
||||
var args = _.map(('year month day '
|
||||
+ 'years months weeks days '
|
||||
+ 'weekday leapdays yearday nlyearday').split(' '), function (arg) {
|
||||
switch (arg) {
|
||||
case 'years':case 'months':case 'days':case 'leapdays':case 'weeks':
|
||||
return [arg, zero];
|
||||
case 'year':case 'month':case 'day':case 'weekday':
|
||||
case 'yearday':case 'nlyearday':
|
||||
return [arg, null];
|
||||
default:
|
||||
throw new Error("Unknown relativedelta argument " + arg);
|
||||
}
|
||||
});
|
||||
args.unshift('*');
|
||||
var utils = {
|
||||
divmod: function (x, y) {
|
||||
var rem = x % y;
|
||||
return {
|
||||
div: (x - rem) / y,
|
||||
mod: rem
|
||||
};
|
||||
},
|
||||
monthrange: function (year, month) {
|
||||
if (month < 1 || month > 12) {
|
||||
throw new Error("Illegal month " + month);
|
||||
}
|
||||
|
||||
var day1 = this.weekday(year, month, 1);
|
||||
var ndays = this.mdays[month] + (month == this.February && this.isleap(year));
|
||||
return [day1, ndays];
|
||||
},
|
||||
weekday: function (year, month, day) {
|
||||
var date = py.PY_call(datetime.date, [year, month, day]);
|
||||
return py.PY_call(py.PY_getAttr(date, 'weekday'));
|
||||
},
|
||||
isleap: function (year) {
|
||||
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
||||
},
|
||||
mdays: [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
January: 1,
|
||||
February: 2
|
||||
};
|
||||
var relativedelta = py.type('relativedelta', null, {
|
||||
__init__: function () {
|
||||
this.ops = py.PY_parseArgs(arguments, args);
|
||||
this.ops.days = py.float.fromJSON(
|
||||
asJS(this.ops.days) + asJS(this.ops.weeks) * 7
|
||||
);
|
||||
|
||||
var yday = zero;
|
||||
if (this.ops.nlyearday) {
|
||||
yday = this.ops.nlyearday;
|
||||
} else if (this.ops.yearday) {
|
||||
yday = this.ops.yearday;
|
||||
if (asJS(this.ops.yearday) > 59) {
|
||||
this.ops.leapdays = py.float.fromJS(-1);
|
||||
}
|
||||
}
|
||||
if (py.PY_isTrue(yday)) {
|
||||
var ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
||||
243, 273, 304, 334, 366];
|
||||
for(var idx=0; idx<ydayidx.length; ++idx) {
|
||||
var ydays = ydayidx[idx];
|
||||
if (asJS(yday) <= ydays) {
|
||||
this.ops.month = py.float.fromJSON(idx+1);
|
||||
if (!idx) {
|
||||
this.ops.day = yday;
|
||||
} else {
|
||||
this.ops.day = py.PY_subtract(
|
||||
yday,
|
||||
py.float.fromJSON(ydayidx[idx-1])
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx === ydayidx.length) {
|
||||
throw new Error("Invalid year day (" + asJS(yday) + ")");
|
||||
}
|
||||
}
|
||||
this._fix();
|
||||
},
|
||||
_fix: function () {
|
||||
var months = asJS(this.ops.months);
|
||||
if (Math.abs(months) > 11) {
|
||||
var s = months > 0 ? 1 : -1;
|
||||
var r = utils.divmod(months * s, 12);
|
||||
this.ops.months = py.float.fromJSON(r.mod*s);
|
||||
this.ops.years = py.float.fromJSON(
|
||||
asJS(this.ops.years) + r.div*s);
|
||||
}
|
||||
this._has_time = 0;
|
||||
},
|
||||
__add__: function (other) {
|
||||
if (!py.PY_isInstance(other, datetime.date)) {
|
||||
return py.NotImplemented;
|
||||
}
|
||||
// TODO: test this whole mess
|
||||
var year = asJS(this.ops.year) || asJS(other.year);
|
||||
if (asJS(this.ops.years)) {
|
||||
year += asJS(this.ops.years);
|
||||
}
|
||||
|
||||
var year = (asJS(this.ops.year) || asJS(other.year)) + asJS(this.ops.years);
|
||||
var month = asJS(this.ops.month) || asJS(other.month);
|
||||
if (asJS(this.ops.months)) {
|
||||
month += asJS(this.ops.months);
|
||||
// FIXME: no divmod in JS?
|
||||
while (month < 1) {
|
||||
year -= 1;
|
||||
month += 12;
|
||||
var months;
|
||||
if (months = asJS(this.ops.months)) {
|
||||
if (Math.abs(months) < 1 || Math.abs(months) > 12) {
|
||||
throw new Error("Can only use relative months between -12 and +12");
|
||||
}
|
||||
while (month > 12) {
|
||||
month += months;
|
||||
if (month > 12) {
|
||||
year += 1;
|
||||
month -= 12;
|
||||
}
|
||||
if (month < 1) {
|
||||
year -= 1;
|
||||
month += 12;
|
||||
}
|
||||
}
|
||||
|
||||
var lastMonthDay = new Date(year, month, 0).getDate();
|
||||
var day = asJS(this.ops.day) || asJS(other.day);
|
||||
if (day > lastMonthDay) { day = lastMonthDay; }
|
||||
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
|
||||
if (days_offset) {
|
||||
day = new Date(year, month-1, day + days_offset).getDate();
|
||||
var day = Math.min(utils.monthrange(year, month)[1],
|
||||
asJS(this.ops.day) || asJS(other.day));
|
||||
|
||||
var repl = {
|
||||
year: py.float.fromJSON(year),
|
||||
month: py.float.fromJSON(month),
|
||||
day: py.float.fromJSON(day)
|
||||
};
|
||||
|
||||
var days = asJS(this.ops.days);
|
||||
if (py.PY_isTrue(this.ops.leapdays) && month > 2 && utils.isleap(year)) {
|
||||
days += asJS(this.ops.leapdays);
|
||||
}
|
||||
// TODO: leapdays?
|
||||
// TODO: hours, minutes, seconds? Not used in XML domains
|
||||
// TODO: weekday?
|
||||
// FIXME: use date.replace
|
||||
return py.PY_call(datetime.date, [
|
||||
py.float.fromJSON(year),
|
||||
py.float.fromJSON(month),
|
||||
py.float.fromJSON(day)
|
||||
]);
|
||||
|
||||
var ret = py.PY_add(
|
||||
py.PY_call(py.PY_getAttr(other, 'replace'), repl),
|
||||
py.PY_call(datetime.timedelta, {
|
||||
days: py.float.fromJSON(days)
|
||||
})
|
||||
);
|
||||
|
||||
if (this.ops.weekday) {
|
||||
// FIXME: only handles numeric weekdays, not decorated
|
||||
var weekday = asJS(this.ops.weekday), nth = 1;
|
||||
var jumpdays = (Math.abs(nth) - 1) * 7;
|
||||
|
||||
var ret_weekday = asJS(py.PY_call(py.PY_getAttr(ret, 'weekday')));
|
||||
if (nth > 0) {
|
||||
jumpdays += (7-ret_weekday+weekday) % 7;
|
||||
} else {
|
||||
jumpdays += (ret_weekday - weekday) % 7;
|
||||
jumpdays *= -1;
|
||||
}
|
||||
ret = py.PY_add(
|
||||
ret,
|
||||
py.PY_call(datetime.timedelta, {
|
||||
days: py.float.fromJSON(jumpdays)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
__radd__: function (other) {
|
||||
return this.__add__(other);
|
||||
},
|
||||
|
||||
__sub__: function (other) {
|
||||
if (!py.PY_isInstance(other, datetime.date)) {
|
||||
return py.NotImplemented;
|
||||
}
|
||||
// TODO: test this whole mess
|
||||
var year = asJS(this.ops.year) || asJS(other.year);
|
||||
if (asJS(this.ops.years)) {
|
||||
year -= asJS(this.ops.years);
|
||||
}
|
||||
|
||||
var month = asJS(this.ops.month) || asJS(other.month);
|
||||
if (asJS(this.ops.months)) {
|
||||
month -= asJS(this.ops.months);
|
||||
// FIXME: no divmod in JS?
|
||||
while (month < 1) {
|
||||
year -= 1;
|
||||
month += 12;
|
||||
}
|
||||
while (month > 12) {
|
||||
year += 1;
|
||||
month -= 12;
|
||||
}
|
||||
}
|
||||
|
||||
var lastMonthDay = new Date(year, month, 0).getDate();
|
||||
var day = asJS(this.ops.day) || asJS(other.day);
|
||||
if (day > lastMonthDay) { day = lastMonthDay; }
|
||||
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
|
||||
if (days_offset) {
|
||||
day = new Date(year, month-1, day - days_offset).getDate();
|
||||
}
|
||||
// TODO: leapdays?
|
||||
// TODO: hours, minutes, seconds? Not used in XML domains
|
||||
// TODO: weekday?
|
||||
return py.PY_call(datetime.date, [
|
||||
py.float.fromJSON(year),
|
||||
py.float.fromJSON(month),
|
||||
py.float.fromJSON(day)
|
||||
]);
|
||||
},
|
||||
__rsub__: function (other) {
|
||||
return this.__sub__(other);
|
||||
return this.__neg__().__radd__(other);
|
||||
},
|
||||
__neg__: function () {
|
||||
return py.PY_call(relativedelta, {
|
||||
years: py.PY_negative(this.ops.years),
|
||||
months: py.PY_negative(this.ops.months),
|
||||
days: py.PY_negative(this.ops.days),
|
||||
leapdays: this.ops.leapdays,
|
||||
year: this.ops.year,
|
||||
month: this.ops.month,
|
||||
day: this.ops.day,
|
||||
weekday: this.ops.weekday
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -261,9 +261,21 @@ openerp.testing.section('eval.types', {
|
||||
});
|
||||
test('relastivedelta', function (instance) {
|
||||
strictEqual(
|
||||
py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=-1)).strftime('%Y-%m-%d 23:59:59')",
|
||||
py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=1)).strftime('%Y-%m-%d')",
|
||||
instance.web.pyeval.context()),
|
||||
"2012-02-14 23:59:59");
|
||||
"2012-02-16");
|
||||
strictEqual(
|
||||
py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=-1)).strftime('%Y-%m-%d')",
|
||||
instance.web.pyeval.context()),
|
||||
"2012-02-14");
|
||||
strictEqual(
|
||||
py.eval("(datetime.date(2012, 2, 1) + relativedelta(days=-1)).strftime('%Y-%m-%d')",
|
||||
instance.web.pyeval.context()),
|
||||
'2012-01-31');
|
||||
strictEqual(
|
||||
py.eval("(datetime.date(2015,2,5)+relativedelta(days=-6,weekday=0)).strftime('%Y-%m-%d')",
|
||||
instance.web.pyeval.context()),
|
||||
'2015-02-02');
|
||||
});
|
||||
test('datetime.tojson', function (instance) {
|
||||
var result = py.eval(
|
||||
|
||||
@@ -327,6 +327,9 @@
|
||||
<t t-call="web.assets_common"/>
|
||||
<t t-call="web.assets_backend"/>
|
||||
|
||||
<style>
|
||||
body { overflow: auto !important; }
|
||||
</style>
|
||||
<script type="text/javascript" id="qunit_config">
|
||||
localStorage.clear();
|
||||
QUnit.config.testTimeout = 5 * 60 * 1000;
|
||||
|
||||
@@ -14,6 +14,7 @@ import werkzeug.wrappers
|
||||
from PIL import Image
|
||||
|
||||
import openerp
|
||||
from openerp.addons.web.controllers.main import WebClient
|
||||
from openerp.addons.web import http
|
||||
from openerp.http import request, STATIC_CACHE
|
||||
from openerp.tools import image_save_for_web
|
||||
@@ -269,6 +270,13 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
irt.create(request.cr, request.uid, new_trans)
|
||||
return True
|
||||
|
||||
@http.route('/website/translations', type='json', auth="public", website=True)
|
||||
def get_website_translations(self, lang):
|
||||
module_obj = request.registry['ir.module.module']
|
||||
module_ids = module_obj.search(request.cr, request.uid, [('name', 'ilike', 'website'), ('state', '=', 'installed')], context=request.context)
|
||||
modules = [x['name'] for x in module_obj.read(request.cr, request.uid, module_ids, ['name'], context=request.context)]
|
||||
return WebClient().translations(mods=modules, lang=lang)
|
||||
|
||||
@http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
|
||||
def attach(self, func, upload=None, url=None, disable_optimization=None):
|
||||
# the upload argument doesn't allow us to access the files if more than
|
||||
|
||||
@@ -305,14 +305,32 @@
|
||||
if ($('html').data('website-id')) {
|
||||
website.id = $('html').data('website-id');
|
||||
website.session = new openerp.Session();
|
||||
var modules = ['website'];
|
||||
return openerp._t.database.load_translations(website.session, modules, website.get_context().lang);
|
||||
return openerp.jsonRpc('/website/translations', 'call', {'lang': website.get_context().lang})
|
||||
.then(function(trans) {
|
||||
openerp._t.database.set_bundle(trans);});
|
||||
}
|
||||
}).then(function () {
|
||||
var templates = openerp.qweb.templates;
|
||||
var keys = _.keys(templates);
|
||||
for (var i = 0; i < keys.length; i++){
|
||||
treat_node(templates[keys[i]]);
|
||||
}
|
||||
}).promise();
|
||||
}
|
||||
return all_ready;
|
||||
};
|
||||
|
||||
function treat_node(node){
|
||||
if(node.nodeType === 3) {
|
||||
if(node.nodeValue.match(/\S/)){
|
||||
node.nodeValue = openerp._t($.trim(node.nodeValue));
|
||||
}
|
||||
}
|
||||
else if(node.nodeType === 1 && node.hasChildNodes()) {
|
||||
_.each(node.childNodes, function(subnode) {treat_node(subnode);});
|
||||
}
|
||||
};
|
||||
|
||||
website.inject_tour = function() {
|
||||
// if a tour is active inject tour js
|
||||
};
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
'use strict';
|
||||
|
||||
var _t = openerp._t;
|
||||
|
||||
var website = openerp.website;
|
||||
website.ready().done(function() {
|
||||
openerp.Tour.register({
|
||||
id: 'banner',
|
||||
name: _t("Build a page"),
|
||||
@@ -100,5 +101,6 @@
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
var $edit_button = $("button[data-action=edit]");
|
||||
$edit_button.removeClass("hidden");
|
||||
$edit_button.text("Translate");
|
||||
|
||||
if(website.no_editor) {
|
||||
$edit_button.removeProp('disabled');
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
'use strict';
|
||||
|
||||
var _t = openerp._t;
|
||||
|
||||
var website = openerp.website;
|
||||
website.ready().done(function() {
|
||||
openerp.Tour.register({
|
||||
id: 'blog',
|
||||
name: _t("Create a blog post"),
|
||||
@@ -89,5 +90,6 @@
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
'use strict';
|
||||
|
||||
var _t = openerp._t;
|
||||
|
||||
var website = openerp.website;
|
||||
website.ready().done(function() {
|
||||
openerp.Tour.register({
|
||||
id: 'event',
|
||||
name: _t("Create an event"),
|
||||
@@ -86,5 +87,6 @@
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
website.ready().done(function() {
|
||||
openerp.Tour.register({
|
||||
id: 'event_buy_tickets',
|
||||
name: "Buy tickets for the Conference on Business Apps",
|
||||
@@ -92,4 +93,5 @@
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
}());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
openerp.Tour.register({
|
||||
id: 'shop_buy_product',
|
||||
name: "Try to buy products",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
'use strict';
|
||||
|
||||
var _t = openerp._t;
|
||||
|
||||
var website = openerp.website;
|
||||
website.ready().done(function() {
|
||||
openerp.Tour.register({
|
||||
id: 'shop',
|
||||
name: _t("Create a product"),
|
||||
@@ -107,5 +108,6 @@
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var _t = openerp._t;
|
||||
var steps = openerp.Tour.tours.shop_buy_product.steps;
|
||||
for (var k=0; k<steps.length; k++) {
|
||||
if (steps[k].title === "click on add to cart") {
|
||||
steps.splice(k+1, 0, {
|
||||
title: "click in modal on 'Proceed to checkout' button",
|
||||
title: _t("click in modal on 'Proceed to checkout' button"),
|
||||
element: '.modal a:contains("Proceed to checkout")',
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Italy, 3 March 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,
|
||||
|
||||
Leonardo Donelli learts92@gmail.com https://github.com/LeartS
|
||||
@@ -0,0 +1,11 @@
|
||||
Belgium, 02/03/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,
|
||||
|
||||
Yenthe Van Ginneken yenthevg@gmail.com https://github.com/Yenthe666
|
||||
+31
-3
@@ -335,6 +335,17 @@ website's rich text edition.
|
||||
``t-field-options`` can be used to customize fields, the most common option
|
||||
is ``widget``, other options are field- or widget-dependent.
|
||||
|
||||
debugging
|
||||
---------
|
||||
|
||||
``t-debug``
|
||||
invokes a debugger using PDB's ``set_trace`` API. The parameter should
|
||||
be the name of a module, on which a ``set_trace`` method is called::
|
||||
|
||||
<t t-debug="pdb"/>
|
||||
|
||||
is equivalent to ``importlib.import_module("pdb").set_trace()``
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
@@ -477,13 +488,30 @@ The javascript QWeb implementation provides a few debugging hooks:
|
||||
|
||||
``t-log``
|
||||
takes an expression parameter, evaluates the expression during rendering
|
||||
and logs its result with ``console.log``
|
||||
and logs its result with ``console.log``::
|
||||
|
||||
<t t-set="foo" t-value="42"/>
|
||||
<t t-log="foo"/>
|
||||
|
||||
will print ``42`` to the console
|
||||
``t-debug``
|
||||
triggers a debugger breakpoint during template rendering
|
||||
triggers a debugger breakpoint during template rendering::
|
||||
|
||||
<t t-if="a_test">
|
||||
<t t-debug="">
|
||||
</t>
|
||||
|
||||
will stop execution if debugging is active (exact condition depend on the
|
||||
browser and its development tools)
|
||||
``t-js``
|
||||
the node's body is javascript code executed during template rendering.
|
||||
Takes a ``context`` parameter, which is the name under which the rendering
|
||||
context will be available in the ``t-js``'s body
|
||||
context will be available in the ``t-js``'s body::
|
||||
|
||||
<t t-set="foo" t-value="42"/>
|
||||
<t t-js="ctx">
|
||||
console.log("Foo is", ctx.foo);
|
||||
</t>
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
@@ -63,6 +63,12 @@ def raise_qweb_exception(etype=None, **kw):
|
||||
e.qweb['cause'] = original
|
||||
raise
|
||||
|
||||
def _build_attribute(name, value):
|
||||
value = escape(value)
|
||||
if isinstance(name, unicode): name = name.encode('utf-8')
|
||||
if isinstance(value, unicode): value = value.encode('utf-8')
|
||||
return ' %s="%s"' % (name, value)
|
||||
|
||||
class FileSystemLoader(object):
|
||||
def __init__(self, path):
|
||||
# TODO: support multiple files #add_file() + add cache
|
||||
@@ -276,8 +282,6 @@ class QWeb(orm.AbstractModel):
|
||||
self, element, attribute_name, attribute_value, qwebcontext)
|
||||
for att, val in attrs:
|
||||
if not val: continue
|
||||
if not isinstance(val, str):
|
||||
val = unicode(val).encode('utf-8')
|
||||
generated_attributes += self.render_attribute(element, att, val, qwebcontext)
|
||||
break
|
||||
else:
|
||||
@@ -340,7 +344,7 @@ class QWeb(orm.AbstractModel):
|
||||
return "<%s%s/>" % (name, generated_attributes)
|
||||
|
||||
def render_attribute(self, element, name, value, qwebcontext):
|
||||
return ' %s="%s"' % (name, escape(value))
|
||||
return _build_attribute(name, value)
|
||||
|
||||
def render_text(self, text, element, qwebcontext):
|
||||
return text.encode('utf-8')
|
||||
@@ -634,7 +638,7 @@ class FieldConverter(osv.AbstractModel):
|
||||
if inherit_branding:
|
||||
# add branding attributes
|
||||
g_att += ''.join(
|
||||
' %s="%s"' % (name, escape(value))
|
||||
_build_attribute(name, value)
|
||||
for name, value in self.attributes(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context)
|
||||
|
||||
@@ -391,6 +391,8 @@ class view(osv.osv):
|
||||
:rtype: list of tuples
|
||||
:return: [(view_arch,view_id), ...]
|
||||
"""
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
user = self.pool['res.users'].browse(cr, 1, uid, context=context)
|
||||
user_groups = frozenset(user.groups_id or ())
|
||||
@@ -401,13 +403,13 @@ class view(osv.osv):
|
||||
['mode', '=', 'extension'],
|
||||
['active', '=', True],
|
||||
]
|
||||
if self.pool._init:
|
||||
if self.pool._init and not context.get('load_all_views'):
|
||||
# Module init currently in progress, only consider views from
|
||||
# modules whose code is already loaded
|
||||
conditions.extend([
|
||||
'|',
|
||||
['model_ids.module', 'in', tuple(self.pool._init_modules)],
|
||||
['id', 'in', context and context.get('check_view_ids') or (0,)],
|
||||
['id', 'in', context.get('check_view_ids') or (0,)],
|
||||
])
|
||||
view_ids = self.search(cr, uid, conditions, context=context)
|
||||
|
||||
@@ -603,10 +605,9 @@ class view(osv.osv):
|
||||
parent_view = self.read_combined(
|
||||
cr, uid, v.inherit_id.id, fields=fields, context=context)
|
||||
arch_tree = etree.fromstring(parent_view['arch'])
|
||||
self.apply_inheritance_specs(
|
||||
arch_tree = self.apply_inheritance_specs(
|
||||
cr, uid, arch_tree, view_arch, parent_view['id'], context=context)
|
||||
|
||||
|
||||
if context.get('inherit_branding'):
|
||||
arch_tree.attrib.update({
|
||||
'data-oe-model': 'ir.ui.view',
|
||||
@@ -1193,7 +1194,8 @@ class view(osv.osv):
|
||||
""", (model,))
|
||||
|
||||
ids = map(itemgetter(0), cr.fetchall())
|
||||
return self._check_xml(cr, uid, ids)
|
||||
context = dict(load_all_views=True)
|
||||
return self._check_xml(cr, uid, ids, context=context)
|
||||
|
||||
def _validate_module_views(self, cr, uid, module):
|
||||
"""Validate architecture of all the views of a given module"""
|
||||
|
||||
@@ -685,6 +685,20 @@ class test_views(ViewCase):
|
||||
)
|
||||
self.assertTrue(validate()) # inherited view
|
||||
|
||||
# validation of a second inherited view (depending on 1st)
|
||||
self._insert_view(
|
||||
name='inherited view 2',
|
||||
model=model,
|
||||
priority=5,
|
||||
inherit_id=vid,
|
||||
arch_db="""<?xml version="1.0"?>
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="target"/>
|
||||
</xpath>
|
||||
""",
|
||||
)
|
||||
self.assertTrue(validate()) # inherited view
|
||||
|
||||
def test_view_inheritance(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
|
||||
|
||||
+6
-2
@@ -528,7 +528,7 @@ class Field(object):
|
||||
@property
|
||||
def base_field(self):
|
||||
""" Return the base field of an inherited field, or `self`. """
|
||||
return self.related_field if self.inherited else self
|
||||
return self.related_field.base_field if self.inherited else self
|
||||
|
||||
#
|
||||
# Setup of field triggers
|
||||
@@ -1482,7 +1482,11 @@ class Many2one(_Relational):
|
||||
# many2one field value (id and name) depends on the current record's
|
||||
# access rights, and not the value's access rights.
|
||||
try:
|
||||
return value.sudo().name_get()[0]
|
||||
value_sudo = value.sudo()
|
||||
# performance trick: make sure that all records of the same
|
||||
# model as value in value.env will be prefetched in value_sudo.env
|
||||
value_sudo.env.prefetch[value._name].update(value.env.prefetch[value._name])
|
||||
return value_sudo.name_get()[0]
|
||||
except MissingError:
|
||||
# Should not happen, unless the foreign key is missing.
|
||||
return False
|
||||
|
||||
+55
-38
@@ -3155,17 +3155,20 @@ class BaseModel(object):
|
||||
fields = self.check_field_access_rights('read', fields)
|
||||
|
||||
# split fields into stored and computed fields
|
||||
stored, computed = [], []
|
||||
stored, inherited, computed = [], [], []
|
||||
for name in fields:
|
||||
if name in self._columns:
|
||||
stored.append(name)
|
||||
elif name in self._fields:
|
||||
computed.append(name)
|
||||
field = self._fields[name]
|
||||
if field.inherited and field.base_field.column:
|
||||
inherited.append(name)
|
||||
else:
|
||||
_logger.warning("%s.read() with unknown field '%s'", self._name, name)
|
||||
|
||||
# fetch stored fields from the database to the cache
|
||||
self._read_from_database(stored)
|
||||
self._read_from_database(stored, inherited)
|
||||
|
||||
# retrieve results from records; this takes values from the cache and
|
||||
# computes remaining fields
|
||||
@@ -3234,50 +3237,61 @@ class BaseModel(object):
|
||||
self._cache[field] = FailedValue(e)
|
||||
|
||||
@api.multi
|
||||
def _read_from_database(self, field_names):
|
||||
def _read_from_database(self, field_names, inherited_field_names=[]):
|
||||
""" Read the given fields of the records in `self` from the database,
|
||||
and store them in cache. Access errors are also stored in cache.
|
||||
|
||||
:param field_names: list of column names of model `self`; all those
|
||||
fields are guaranteed to be read
|
||||
:param inherited_field_names: list of column names from parent
|
||||
models; some of those fields may not be read
|
||||
"""
|
||||
env = self.env
|
||||
cr, user, context = env.args
|
||||
|
||||
# FIXME: The query construction needs to be rewritten using the internal Query
|
||||
# object, as in search(), to avoid ambiguous column references when
|
||||
# reading/sorting on a table that is auto_joined to another table with
|
||||
# common columns (e.g. the magical columns)
|
||||
# make a query object for selecting ids, and apply security rules to it
|
||||
query = Query(['"%s"' % self._table], ['"%s".id IN %%s' % self._table], [])
|
||||
self._apply_ir_rules(query, 'read')
|
||||
order_str = self._generate_order_by(None, query)
|
||||
|
||||
# Construct a clause for the security rules.
|
||||
# 'tables' holds the list of tables necessary for the SELECT, including
|
||||
# the ir.rule clauses, and contains at least self._table.
|
||||
rule_clause, rule_params, tables = env['ir.rule'].domain_get(self._name, 'read')
|
||||
# determine the fields that are stored as columns in tables;
|
||||
# for the sake of simplicity, discard inherited translated fields
|
||||
fields = map(self._fields.get, field_names + inherited_field_names)
|
||||
fields_pre = [
|
||||
field
|
||||
for field in fields
|
||||
if field.base_field.column._classic_write
|
||||
if not (field.inherited and field.base_field.column.translate)
|
||||
]
|
||||
|
||||
# determine the fields that are stored as columns in self._table
|
||||
fields_pre = [f for f in field_names if self._columns[f]._classic_write]
|
||||
|
||||
# we need fully-qualified column names in case len(tables) > 1
|
||||
def qualify(f):
|
||||
if isinstance(self._columns.get(f), fields.binary) and \
|
||||
context.get('bin_size_%s' % f, context.get('bin_size')):
|
||||
# PG 9.2 introduces conflicting pg_size_pretty(numeric) -> need ::cast
|
||||
return 'pg_size_pretty(length(%s."%s")::bigint) as "%s"' % (self._table, f, f)
|
||||
# the query may involve several tables: we need fully-qualified names
|
||||
def qualify(field):
|
||||
col = field.name
|
||||
if field.inherited:
|
||||
res = self._inherits_join_calc(field.name, query)
|
||||
else:
|
||||
return '%s."%s"' % (self._table, f)
|
||||
qual_names = map(qualify, set(fields_pre + ['id']))
|
||||
res = '"%s"."%s"' % (self._table, col)
|
||||
if field.type == 'binary' and (context.get('bin_size') or context.get('bin_size_' + col)):
|
||||
# PG 9.2 introduces conflicting pg_size_pretty(numeric) -> need ::cast
|
||||
res = 'pg_size_pretty(length(%s)::bigint) as "%s"' % (res, col)
|
||||
return res
|
||||
|
||||
query = """ SELECT %(qual_names)s FROM %(tables)s
|
||||
WHERE %(table)s.id IN %%s AND (%(extra)s)
|
||||
ORDER BY %(order)s
|
||||
""" % {
|
||||
'qual_names': ",".join(qual_names),
|
||||
'tables': ",".join(tables),
|
||||
'table': self._table,
|
||||
'extra': " OR ".join(rule_clause) if rule_clause else "TRUE",
|
||||
'order': self._parent_order or self._order,
|
||||
}
|
||||
qual_names = map(qualify, set(fields_pre + [self._fields['id']]))
|
||||
|
||||
# determine the actual query to execute
|
||||
from_clause, where_clause, where_params = query.get_sql()
|
||||
query_str = """ SELECT %(qual_names)s FROM %(from_clause)s
|
||||
WHERE %(where_clause)s %(order_str)s
|
||||
""" % {
|
||||
'qual_names': ",".join(qual_names),
|
||||
'from_clause': from_clause,
|
||||
'where_clause': where_clause,
|
||||
'order_str': order_str,
|
||||
}
|
||||
|
||||
result = []
|
||||
for sub_ids in cr.split_for_in_conditions(self.ids):
|
||||
cr.execute(query, [tuple(sub_ids)] + rule_params)
|
||||
cr.execute(query_str, [tuple(sub_ids)] + where_params)
|
||||
result.extend(cr.dictfetchall())
|
||||
|
||||
ids = [vals['id'] for vals in result]
|
||||
@@ -3286,8 +3300,9 @@ class BaseModel(object):
|
||||
# translate the fields if necessary
|
||||
if context.get('lang'):
|
||||
ir_translation = env['ir.translation']
|
||||
for f in fields_pre:
|
||||
if self._columns[f].translate:
|
||||
for field in fields_pre:
|
||||
if not field.inherited and field.column.translate:
|
||||
f = field.name
|
||||
#TODO: optimize out of this loop
|
||||
res_trans = ir_translation._get_ids(
|
||||
'%s,%s' % (self._name, f), 'model', context['lang'], ids)
|
||||
@@ -3295,9 +3310,10 @@ class BaseModel(object):
|
||||
vals[f] = res_trans.get(vals['id'], False) or vals[f]
|
||||
|
||||
# apply the symbol_get functions of the fields we just read
|
||||
for f in fields_pre:
|
||||
symbol_get = self._columns[f]._symbol_get
|
||||
for field in fields_pre:
|
||||
symbol_get = field.base_field.column._symbol_get
|
||||
if symbol_get:
|
||||
f = field.name
|
||||
for vals in result:
|
||||
vals[f] = symbol_get(vals[f])
|
||||
|
||||
@@ -3306,7 +3322,8 @@ class BaseModel(object):
|
||||
record = self.browse(vals['id'])
|
||||
record._cache.update(record._convert_to_cache(vals, validate=False))
|
||||
|
||||
# determine the fields that must be processed now
|
||||
# determine the fields that must be processed now;
|
||||
# for the sake of simplicity, we ignore inherited fields
|
||||
fields_post = [f for f in field_names if not self._columns[f]._classic_write]
|
||||
|
||||
# Compute POST fields, grouped by multi
|
||||
|
||||
Reference in New Issue
Block a user