[MERGE] forward port branch 11.0 up to a69f117bbf
This commit is contained in:
@@ -9,6 +9,7 @@ var Domain = require('web.Domain');
|
||||
var FormController = require('web.FormController');
|
||||
var FormRenderer = require('web.FormRenderer');
|
||||
var FormView = require('web.FormView');
|
||||
var pyUtils = require('web.pyeval'); // do not forwardport this to 12.0
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
var _t = core._t;
|
||||
@@ -236,7 +237,6 @@ var BoardRenderer = FormRenderer.extend({
|
||||
*/
|
||||
_createController: function (params) {
|
||||
var self = this;
|
||||
var context = params.context.eval();
|
||||
return this._rpc({
|
||||
route: '/web/action/load',
|
||||
params: {action_id: params.actionID}
|
||||
@@ -246,18 +246,22 @@ var BoardRenderer = FormRenderer.extend({
|
||||
// the action does not exist anymore
|
||||
return $.when();
|
||||
}
|
||||
var rawContext = new Context(params.context, action.context);
|
||||
var context = pyUtils.eval('context', rawContext);
|
||||
var domain = params.domain || pyUtils.eval('domain', action.domain || '[]', action.context);
|
||||
var viewType = params.viewType || action.views[0][1];
|
||||
var view = _.find(action.views, function (descr) {
|
||||
return descr[1] === params.viewType;
|
||||
}) || [false, params.viewType];
|
||||
return descr[1] === viewType;
|
||||
}) || [false, viewType];
|
||||
return self.loadViews(action.res_model, context, [view])
|
||||
.then(function (viewsInfo) {
|
||||
var viewInfo = viewsInfo[params.viewType];
|
||||
var View = viewRegistry.get(params.viewType);
|
||||
var viewInfo = viewsInfo[viewType];
|
||||
var View = viewRegistry.get(viewType);
|
||||
var view = new View(viewInfo, {
|
||||
action: action,
|
||||
context: context,
|
||||
domain: params.domain,
|
||||
groupBy: context.group_by,
|
||||
domain: domain,
|
||||
groupBy: context.group_by || [],
|
||||
modelName: action.res_model,
|
||||
hasSelectors: false,
|
||||
});
|
||||
|
||||
@@ -191,6 +191,57 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('can render an action without view_mode attribute', function (assert) {
|
||||
// The view_mode attribute is automatically set to the 'action' nodes when
|
||||
// the action is added to the dashboard using the 'Add to dashboard' button
|
||||
// in the searchview. However, other dashboard views can be written by hand
|
||||
// (see openacademy tutorial), and in this case, we don't want hardcode
|
||||
// action's params (like context or domain), as the dashboard can directly
|
||||
// retrieve them from the action. Same applies for the view_type, as the
|
||||
// first view of the action can be used, by default.
|
||||
assert.expect(2);
|
||||
|
||||
var form = createView({
|
||||
View: FormView, // replace by BoardView when forwarported to saas-11.3
|
||||
model: 'board',
|
||||
data: this.data,
|
||||
arch: '<form string="My Dashboard">' +
|
||||
'<board style="2-1">' +
|
||||
'<column>' +
|
||||
'<action string="ABC" name="51" context="{\'a\': 1}"></action>' +
|
||||
'</column>' +
|
||||
'</board>' +
|
||||
'</form>',
|
||||
archs: {
|
||||
'partner,4,list':
|
||||
'<tree string="Partner"><field name="foo"/></tree>',
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/board/static/src/img/layout_1-1-1.png') {
|
||||
return $.when();
|
||||
}
|
||||
if (route === '/web/action/load') {
|
||||
return $.when({
|
||||
context: '{"b": 2}',
|
||||
domain: '[["foo", "=", "yop"]]',
|
||||
res_model: 'partner',
|
||||
views: [[4, 'list'], [false, 'form']],
|
||||
});
|
||||
}
|
||||
if (route === '/web/dataset/search_read') {
|
||||
assert.deepEqual(args.domain, [['foo', '=', 'yop']],
|
||||
"should use the domain of the action");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.oe_action:contains(ABC) .o_list_view').length, 1,
|
||||
"the list view (first view of action) should have been rendered correctly");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('can sort a sub list', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
odoo.define('mrp.tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var field_registry = require('web.field_registry');
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require("web.test_utils");
|
||||
|
||||
@@ -29,6 +30,49 @@ QUnit.module('mrp', {
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.test("pdf_viewer: upload rendering", function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
testUtils.patch(field_registry.map.pdf_viewer, {
|
||||
on_file_change: function (ev) {
|
||||
ev.target = {files: [new Blob()]};
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
_getURI: function (fileURI) {
|
||||
var res = this._super.apply(this, arguments);
|
||||
assert.step('_getURI');
|
||||
assert.ok(_.str.startsWith(fileURI, 'blob:'));
|
||||
this.PDFViewerApplication = {
|
||||
open: function (URI) {
|
||||
assert.step('open');
|
||||
assert.ok(_.str.startsWith(URI, 'blob:'));
|
||||
},
|
||||
};
|
||||
return 'about:blank';
|
||||
},
|
||||
});
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<form>' +
|
||||
'<field name="document" widget="pdf_viewer"/>' +
|
||||
'</form>',
|
||||
});
|
||||
|
||||
// first upload initialize iframe
|
||||
form.$('input[type="file"]').trigger('change');
|
||||
assert.verifySteps(['_getURI']);
|
||||
// second upload call pdfjs method inside iframe
|
||||
form.$('input[type="file"]').trigger('change');
|
||||
assert.verifySteps(['_getURI', 'open']);
|
||||
|
||||
testUtils.unpatch(field_registry.map.pdf_viewer);
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("bullet_state: basic rendering", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
|
||||
@@ -185,6 +185,8 @@ class StockMoveLine(models.Model):
|
||||
|
||||
ml = super(StockMoveLine, self).create(vals)
|
||||
if ml.state == 'done':
|
||||
if 'qty_done' in vals:
|
||||
ml.move_id.product_uom_qty = ml.move_id.quantity_done
|
||||
if ml.product_id.type == 'product':
|
||||
Quant = self.env['stock.quant']
|
||||
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,rounding_method='HALF-UP')
|
||||
|
||||
@@ -427,6 +427,10 @@ var FieldDate = InputField.extend({
|
||||
this._super.apply(this, arguments);
|
||||
// use the session timezone when formatting dates
|
||||
this.formatOptions.timezone = true;
|
||||
this.datepickerOptions = _.defaults(
|
||||
this.nodeOptions.datepicker || {},
|
||||
{defaultDate: this.value}
|
||||
);
|
||||
},
|
||||
/**
|
||||
* In edit mode, instantiates a DateWidget datepicker and listen to changes.
|
||||
@@ -491,13 +495,7 @@ var FieldDate = InputField.extend({
|
||||
* @private
|
||||
*/
|
||||
_makeDatePicker: function () {
|
||||
return new datepicker.DateWidget(
|
||||
this,
|
||||
_.defaults(
|
||||
this.nodeOptions.datepicker || {},
|
||||
{defaultDate: this.value}
|
||||
)
|
||||
);
|
||||
return new datepicker.DateWidget(this, this.datepickerOptions);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -515,6 +513,17 @@ var FieldDate = InputField.extend({
|
||||
var FieldDateTime = FieldDate.extend({
|
||||
supportedFieldTypes: ['datetime'],
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.value) {
|
||||
var offset = this.getSession().getTZOffset(this.value);
|
||||
var displayedValue = this.value.clone().add(offset, 'minutes');
|
||||
this.datepickerOptions.defaultDate = displayedValue;
|
||||
}
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -545,10 +554,8 @@ var FieldDateTime = FieldDate.extend({
|
||||
* @private
|
||||
*/
|
||||
_makeDatePicker: function () {
|
||||
var value = this.value && this.value.clone().add(this.getSession().getTZOffset(this.value), 'minutes');
|
||||
return new datepicker.DateTimeWidget(this, {defaultDate: value});
|
||||
return new datepicker.DateTimeWidget(this, this.datepickerOptions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the datepicker to the right value rather than the default one.
|
||||
*
|
||||
@@ -1314,19 +1321,23 @@ var FieldPdfViewer = FieldBinaryFile.extend({
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} [fileURI] file URI if specified
|
||||
* @returns {string} the pdf viewer URI
|
||||
*/
|
||||
_getURI: function () {
|
||||
var queryObj = {
|
||||
model: this.model,
|
||||
field: this.name,
|
||||
id: this.res_id,
|
||||
};
|
||||
_getURI: function (fileURI) {
|
||||
var page = this.recordData[this.name + '_page'] || 1;
|
||||
var queryString = $.param(queryObj);
|
||||
var url = encodeURIComponent('/web/image?' + queryString);
|
||||
if (!fileURI) {
|
||||
var queryObj = {
|
||||
model: this.model,
|
||||
field: this.name,
|
||||
id: this.res_id,
|
||||
};
|
||||
var queryString = $.param(queryObj);
|
||||
fileURI = '/web/image?' + queryString
|
||||
}
|
||||
fileURI = encodeURIComponent(fileURI);
|
||||
var viewerURL = '/web/static/lib/pdfjs/web/viewer.html?file=';
|
||||
return viewerURL + url + '#page=' + page;
|
||||
return viewerURL + fileURI + '#page=' + page;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -1370,14 +1381,16 @@ var FieldPdfViewer = FieldBinaryFile.extend({
|
||||
*/
|
||||
on_file_change: function (ev) {
|
||||
this._super.apply(this, arguments);
|
||||
var files = ev.target.files;
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
// TOCheck: is there requirement to fallback on FileReader if browser don't support URL
|
||||
var fileURI = URL.createObjectURL(files[0]);
|
||||
if (this.PDFViewerApplication) {
|
||||
var files = ev.target.files;
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
var file = files[0];
|
||||
// TOCheck: is there requirement to fallback on FileReader if browser don't support URL
|
||||
this.PDFViewerApplication.open(URL.createObjectURL(file), 0);
|
||||
this.PDFViewerApplication.open(fileURI, 0);
|
||||
} else {
|
||||
this.$('.o_pdfview_iframe').attr('src', this._getURI(fileURI));
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -2693,6 +2693,33 @@ QUnit.module('basic_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('datepicker option: daysOfWeekDisabled', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data.partner.fields.datetime.default = "2017-08-02 12:00:05";
|
||||
this.data.partner.fields.datetime.required = true;
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<field name="datetime" ' +
|
||||
'options=\'{"datepicker": {"daysOfWeekDisabled": [0, 6]}}\'/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_create').click();
|
||||
form.$('.o_datepicker_input.o_input').click();
|
||||
assert.ok($('.day:last-child(),.day:nth-child(2)').hasClass('disabled'),
|
||||
'first and last days must be disabled');
|
||||
assert.notOk($('.day:not(:last-child()):not(:nth-child(2))').hasClass('disabled'),
|
||||
'other days must stay clickable');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('FieldMonetary');
|
||||
|
||||
QUnit.test('monetary field in form view', function (assert) {
|
||||
|
||||
@@ -92,17 +92,16 @@
|
||||
<input type="hidden" class="product_id" t-attf-name="optional-product-#{product_index}" t-att-value="product.product_variant_id.id if len(product.product_variant_ids) == 1 else '0'"/>
|
||||
<span t-field="product.image_small" t-options='{"widget": "image"}'/>
|
||||
</td>
|
||||
<td class='td-product_name'>
|
||||
<td class='td-product_name' colspan="2">
|
||||
<div class="pull-left">
|
||||
<strong class="media-heading" t-field="product.name"/>
|
||||
<div class="text-muted small" t-field="product.description_sale"/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="td-qty">
|
||||
<div class="pull-right">
|
||||
<div class="pull-right">
|
||||
<t t-call="website_sale.variants"/>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="td-price">
|
||||
<span class="text-danger oe_default_price oe_optional"
|
||||
t-field="product.lst_price"
|
||||
|
||||
@@ -1348,7 +1348,7 @@ order.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="datefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
|
||||
<field name="datetimefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
|
||||
|
||||
- monetary (FieldMonetary)
|
||||
This is the default field type for fields of type 'monetary'. It is used to
|
||||
|
||||
@@ -127,6 +127,13 @@ def get_view_arch_from_file(filename, xmlid):
|
||||
if node.tag in ('template', 'record'):
|
||||
if node.tag == 'record':
|
||||
field = node.find('field[@name="arch"]')
|
||||
if field is None:
|
||||
if node.find('field[@name="view_id"]') is not None:
|
||||
view_id = node.find('field[@name="view_id"]').attrib.get('ref')
|
||||
ref_id = '%s%s' % ('.' not in view_id and xmlid.split('.')[0] + '.' or '', view_id)
|
||||
return get_view_arch_from_file(filename, ref_id)
|
||||
else:
|
||||
return None
|
||||
_fix_multiple_roots(field)
|
||||
inner = u''.join([etree.tostring(child, encoding='unicode') for child in field.iterchildren()])
|
||||
return field.text + inner
|
||||
|
||||
Reference in New Issue
Block a user