diff --git a/addons/board/static/src/js/board_view.js b/addons/board/static/src/js/board_view.js
index b2d57903a3d..a40348bc319 100644
--- a/addons/board/static/src/js/board_view.js
+++ b/addons/board/static/src/js/board_view.js
@@ -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,
});
diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js
index e777b7b36b7..1329d883696 100644
--- a/addons/board/static/tests/dashboard_tests.js
+++ b/addons/board/static/tests/dashboard_tests.js
@@ -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: '
',
+ archs: {
+ 'partner,4,list':
+ '',
+ },
+ 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);
diff --git a/addons/mrp/static/tests/mrp_tests.js b/addons/mrp/static/tests/mrp_tests.js
index d86adf842c5..eff1104a041 100644
--- a/addons/mrp/static/tests/mrp_tests.js
+++ b/addons/mrp/static/tests/mrp_tests.js
@@ -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:
+ '',
+ });
+
+ // 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);
diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py
index d911481d982..38f064685fb 100644
--- a/addons/stock/models/stock_move_line.py
+++ b/addons/stock/models/stock_move_line.py
@@ -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')
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 0796cbc706a..55532ec6d16 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -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));
}
},
/**
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 98468872ab9..5b36b433a05 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -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:'',
+ 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) {
diff --git a/addons/website_sale_options/views/website_sale_options_templates.xml b/addons/website_sale_options/views/website_sale_options_templates.xml
index 6fb938231f1..36e54779fcc 100644
--- a/addons/website_sale_options/views/website_sale_options_templates.xml
+++ b/addons/website_sale_options/views/website_sale_options_templates.xml
@@ -92,17 +92,16 @@
-
+
-
-
-
+
+
+
- monetary (FieldMonetary)
This is the default field type for fields of type 'monetary'. It is used to
diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py
index ea387ecc1d7..e3016ee4077 100644
--- a/odoo/addons/base/models/ir_ui_view.py
+++ b/odoo/addons/base/models/ir_ui_view.py
@@ -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