summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-08-27 11:16:44 +0200
committerChristophe Simonis <chs@odoo.com>2018-08-27 11:16:44 +0200
commita7a30791def5a033f5e77d42f155bafae0c430cb (patch)
treeba668f1a961ccd7455be33dfa50e0b763f7c360b
parent0da9da4c447eed7dafc10a17d59000a6cfa5f55f (diff)
parenta5187cef1078b01a27e05e761d7592b6857f41f9 (diff)
[MERGE] forward port branch saas-11.4 up to a5187cef107
-rw-r--r--addons/account/models/account_bank_statement.py2
-rw-r--r--addons/account/models/account_move.py2
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js4
-rw-r--r--addons/crm/models/crm_lead.py6
-rw-r--r--addons/hr/models/res_users.py2
-rw-r--r--addons/hr/security/hr_security.xml2
-rw-r--r--addons/hr_expense/models/hr_expense.py2
-rw-r--r--addons/hr_holidays/models/hr_leave_allocation.py4
-rw-r--r--addons/hr_holidays/report/hr_leave_reports.xml2
-rw-r--r--addons/mail/models/mail_channel.py4
-rw-r--r--addons/mail/models/mail_thread.py1
-rw-r--r--addons/mrp/static/src/scss/mrp.scss8
-rw-r--r--addons/mrp/views/mrp_templates.xml1
-rw-r--r--addons/point_of_sale/models/pos_order.py2
-rw-r--r--addons/point_of_sale/views/pos_config_view.xml3
-rw-r--r--addons/stock/models/stock_move.py10
-rw-r--r--addons/stock/tests/test_move.py21
-rw-r--r--addons/web/models/ir_qweb.py1
-rw-r--r--addons/web/static/src/js/chrome/keyboard_navigation_mixin.js8
-rw-r--r--addons/web/static/src/js/core/misc.js25
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js8
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js2
-rw-r--r--addons/web/static/src/scss/fields.scss8
-rw-r--r--addons/web/static/tests/helpers/mock_server.js19
-rw-r--r--addons/web/static/tests/views/graph_tests.js23
-rw-r--r--addons/web/static/tests/views/list_tests.js110
-rw-r--r--addons/website_crm_partner_assign/controllers/main.py2
-rw-r--r--odoo/addons/base/models/ir_http.py2
-rw-r--r--odoo/addons/base/models/ir_qweb.py2
-rw-r--r--odoo/addons/base/models/res_users.py3
-rw-r--r--requirements.txt3
31 files changed, 252 insertions, 40 deletions
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 0a91e072607..9374827096f 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -4,7 +4,7 @@ from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.tools import float_is_zero, pycompat
from odoo.tools import float_compare, float_round, float_repr
-from odoo.tools.misc import formatLang
+from odoo.tools.misc import formatLang, format_date
from odoo.exceptions import UserError, ValidationError
import time
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 61b5bcb9c6c..5b83ebfb0af 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -6,7 +6,7 @@ from collections import OrderedDict
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.exceptions import RedirectWarning, UserError, ValidationError
-from odoo.tools.misc import formatLang
+from odoo.tools.misc import formatLang, format_date
from odoo.tools import float_is_zero, float_compare
from odoo.tools.safe_eval import safe_eval
from odoo.addons import decimal_precision as dp
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index 9bc336af4b8..73b98a2f40b 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -443,6 +443,7 @@ var StatementModel = BasicModel.extend({
model: 'account.reconciliation.widget',
method: 'get_bank_statement_line_data',
args: [ids, excluded_ids],
+ context: self.context,
})
.then(self._formatLine.bind(self));
},
@@ -1175,6 +1176,7 @@ var StatementModel = BasicModel.extend({
model: 'account.reconciliation.widget',
method: 'get_move_lines_for_bank_statement_line',
args: [line.id, line.st_line.partner_id, excluded_ids, filter, offset, limit],
+ context: this.context,
})
.then(this._formatMoveLine.bind(this, handle));
},
@@ -1240,6 +1242,7 @@ var ManualModel = StatementModel.extend({
*/
load: function (context) {
var self = this;
+ this.context = context;
var domain_account_id = [];
if (context && context.company_ids) {
@@ -1550,6 +1553,7 @@ var ManualModel = StatementModel.extend({
model: 'account.reconciliation.widget',
method: 'get_move_lines_for_manual_reconciliation',
args: args,
+ context: this.context,
})
.then(this._formatMoveLine.bind(this, handle));
},
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 67963e8e6d0..43fb9c795aa 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -171,7 +171,7 @@ class Lead(models.Model):
@api.depends('date_open')
def _compute_day_open(self):
""" Compute difference between create date and open date """
- for lead in self.filtered(lambda l: l.date_open):
+ for lead in self.filtered(lambda l: l.date_open and l.create_date):
date_create = fields.Datetime.from_string(lead.create_date)
date_open = fields.Datetime.from_string(lead.date_open)
lead.day_open = abs((date_open - date_create).days)
@@ -179,7 +179,7 @@ class Lead(models.Model):
@api.depends('date_closed')
def _compute_day_close(self):
""" Compute difference between current date and log date """
- for lead in self.filtered(lambda l: l.date_closed):
+ for lead in self.filtered(lambda l: l.date_closed and l.create_date):
date_create = fields.Datetime.from_string(lead.create_date)
date_close = fields.Datetime.from_string(lead.date_closed)
lead.day_close = abs((date_close - date_create).days)
@@ -714,7 +714,7 @@ class Lead(models.Model):
partner_match_domain.append(('partner_id', '=', partner_id))
partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain
if not partner_match_domain:
- return []
+ return self.env['crm.lead']
domain = partner_match_domain
if not include_lost:
domain += ['&', ('active', '=', True), ('probability', '<', 100)]
diff --git a/addons/hr/models/res_users.py b/addons/hr/models/res_users.py
index 08c3d4f6d14..9b1777c5084 100644
--- a/addons/hr/models/res_users.py
+++ b/addons/hr/models/res_users.py
@@ -15,7 +15,7 @@ class User(models.Model):
selection=lambda self: self._get_group_selection('base.module_category_human_resources'),
string="Human Resources", compute='_compute_groups_id', inverse='_inverse_groups_id',
category_xml_id='base.module_category_human_resources',
- help='Officer: The user will be able to approve document created by employees.\nManager: The user will have an access to the human resources configuration as well as statistic reports.')
+ help='Officer: The user will be able to approve document created by employees.\nManager: The user will have access to the human resources configuration as well as statistic reports.')
@api.multi
def write(self, vals):
diff --git a/addons/hr/security/hr_security.xml b/addons/hr/security/hr_security.xml
index ddb4f9c5cbe..09e16244846 100644
--- a/addons/hr/security/hr_security.xml
+++ b/addons/hr/security/hr_security.xml
@@ -10,7 +10,7 @@
<record id="group_hr_manager" model="res.groups">
<field name="name">Manager</field>
- <field name="comment">The user will have an access to the human resources configuration as well as statistic reports.</field>
+ <field name="comment">The user will have access to the human resources configuration as well as statistic reports.</field>
<field name="category_id" ref="base.module_category_human_resources"/>
<field name="implied_ids" eval="[(4, ref('group_hr_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index cd5a123c691..b1d1d0297ef 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -722,6 +722,6 @@ class HrExpenseSheet(models.Model):
for expense_report in self.filtered(lambda hol: hol.state == 'submit'):
self.activity_schedule(
'hr_expense.mail_act_expense_approval',
- user_id=expense_report._get_responsible_for_approval().id)
+ user_id=expense_report.sudo()._get_responsible_for_approval().id)
self.filtered(lambda hol: hol.state == 'approve').activity_feedback(['hr_expense.mail_act_expense_approval'])
self.filtered(lambda hol: hol.state == 'cancel').activity_unlink(['hr_expense.mail_act_expense_approval'])
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index ffaa77d624e..3a0fdde5ba1 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -467,12 +467,12 @@ class HolidaysAllocation(models.Model):
elif allocation.state == 'confirm':
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_approval',
- user_id=allocation._get_responsible_for_approval().id)
+ user_id=allocation.sudo()._get_responsible_for_approval().id)
elif allocation.state == 'validate1':
allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval'])
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_second_approval',
- user_id=allocation._get_responsible_for_approval().id)
+ user_id=allocation.sudo()._get_responsible_for_approval().id)
elif allocation.state == 'validate':
to_do |= allocation
elif allocation.state == 'refuse':
diff --git a/addons/hr_holidays/report/hr_leave_reports.xml b/addons/hr_holidays/report/hr_leave_reports.xml
index 004433a3c62..51f339ca65b 100644
--- a/addons/hr_holidays/report/hr_leave_reports.xml
+++ b/addons/hr_holidays/report/hr_leave_reports.xml
@@ -12,8 +12,6 @@
<separator/>
<filter name="active_types" string="Active Types" domain="[('holiday_status_id.active', '=', True)]" help="Filters only on requests that belong to an leave type that is 'active' (active field is True)"/>
<separator/>
- <filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
- <separator/>
<filter string="My Department Leaves" name="department" domain="[('department_id.manager_id.user_id', '=', uid)]" help="My Department Leaves"/>
<filter name="my_team_leaves" string="My Team Leaves" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="hr_holidays.group_hr_holidays_manager" help="Leaves of Your Team Member"/>
<separator/>
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 1c316673935..3f2a655e1a9 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -621,8 +621,8 @@ class Channel(models.Model):
AND P.partner_id IN %s
AND channel_type LIKE 'chat'
GROUP BY P.channel_id
- HAVING COUNT(P.partner_id) = %s
- """, (tuple(partners_to), len(partners_to),))
+ HAVING array_agg(P.partner_id ORDER BY P.partner_id) = %s
+ """, (tuple(partners_to), sorted(list(partners_to)),))
result = self.env.cr.dictfetchall()
if result:
# get the existing channel between the given partners
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index de36e854077..da0c45051ce 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -1913,6 +1913,7 @@ class MailThread(models.AbstractModel):
if not attachment:
attachment = fname_mapping.get(node.get('data-filename'), '')
if attachment:
+ attachment.generate_access_token()
node.set('src', '/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token))
postprocessed = True
if postprocessed:
diff --git a/addons/mrp/static/src/scss/mrp.scss b/addons/mrp/static/src/scss/mrp.scss
deleted file mode 100644
index a0ce531779f..00000000000
--- a/addons/mrp/static/src/scss/mrp.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-.o_form_view {
- .o_field_pdfviewer {
- width: 100%;
- height: 450px;
- border: 0;
- margin-top: 10px;
- }
-}
diff --git a/addons/mrp/views/mrp_templates.xml b/addons/mrp/views/mrp_templates.xml
index 361a0d0018a..f51f0427e19 100644
--- a/addons/mrp/views/mrp_templates.xml
+++ b/addons/mrp/views/mrp_templates.xml
@@ -3,7 +3,6 @@
<template id="assets_backend" name="mrp assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/mrp/static/src/js/mrp.js"></script>
- <link rel="stylesheet" type="text/scss" href="/mrp/static/src/scss/mrp.scss"/>
<script type="text/javascript" src="/mrp/static/src/js/mrp_bom_report.js"></script>
</xpath>
</template>
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 25072ad578d..41b27d4e979 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -148,7 +148,7 @@ class PosOrder(models.Model):
cash_journal_id = cash_journal[0].id
order.add_payment({
'amount': -pos_order['amount_return'],
- 'payment_date': fields.Datetime.now(),
+ 'payment_date': fields.Date.context_today(self),
'payment_name': _('return'),
'journal': cash_journal_id,
})
diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml
index 4a8a114e641..da106f5ab71 100644
--- a/addons/point_of_sale/views/pos_config_view.xml
+++ b/addons/point_of_sale/views/pos_config_view.xml
@@ -419,7 +419,8 @@
<div class="content-group mt16" attrs="{'invisible': [('module_account', '=', False)]}">
<div class="row mt16">
<label string="Invoice Journal" for="invoice_journal_id" class="col-lg-3 o_light_label"/>
- <field name="invoice_journal_id"/>
+ <field name="invoice_journal_id"
+ attrs="{'required': [('module_account_invoicing', '=', True)]}"/>
</div>
</div>
</div>
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index b760ff82a45..9758f356375 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -891,11 +891,13 @@ class StockMove(models.Model):
assigned_moves = self.env['stock.move']
partially_available_moves = self.env['stock.move']
for move in self.filtered(lambda m: m.state in ['confirmed', 'waiting', 'partially_available']):
+ missing_reserved_uom_quantity = move.product_uom_qty - move.reserved_availability
+ missing_reserved_quantity = move.product_uom._compute_quantity(missing_reserved_uom_quantity, move.product_id.uom_id, rounding_method='HALF-UP')
if move.location_id.should_bypass_reservation()\
or move.product_id.type == 'consu':
# create the move line(s) but do not impact quants
if move.product_id.tracking == 'serial' and (move.picking_type_id.use_create_lots or move.picking_type_id.use_existing_lots):
- for i in range(0, int(move.product_qty - move.reserved_availability)):
+ for i in range(0, int(missing_reserved_quantity)):
self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=1))
else:
to_update = move.move_line_ids.filtered(lambda ml: ml.product_uom_id == move.product_uom and
@@ -906,16 +908,16 @@ class StockMove(models.Model):
not ml.package_id and
not ml.owner_id)
if to_update:
- to_update[0].product_uom_qty += move.product_qty - move.reserved_availability
+ to_update[0].product_uom_qty += missing_reserved_uom_quantity
else:
- self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=move.product_qty - move.reserved_availability))
+ self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=missing_reserved_quantity))
assigned_moves |= move
else:
if not move.move_orig_ids:
if move.procure_method == 'make_to_order':
continue
# If we don't need any quantity, consider the move assigned.
- need = move.product_qty - move.reserved_availability
+ need = missing_reserved_quantity
if float_is_zero(need, precision_rounding=move.product_id.uom_id.rounding):
assigned_moves |= move
continue
diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py
index 4434f45b9bb..53b9b842cc7 100644
--- a/addons/stock/tests/test_move.py
+++ b/addons/stock/tests/test_move.py
@@ -1099,6 +1099,27 @@ class StockMove(TransactionCase):
move_partial._action_assign()
self.assertEqual(move_partial.state, 'assigned')
+ def test_availability_9(self):
+ """ Test the assignment mechanism when the product quantity is increase
+ on a receipt move.
+ """
+ move_receipt = self.env['stock.move'].create({
+ 'name': 'test_receipt_edit',
+ 'location_id': self.supplier_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_dozen.id,
+ 'product_uom_qty': 1.0,
+ })
+
+ move_receipt._action_confirm()
+ move_receipt._action_assign()
+ self.assertEqual(move_receipt.state, 'assigned')
+ move_receipt.product_uom_qty = 3.0
+ move_receipt._action_assign()
+ self.assertEqual(move_receipt.state, 'assigned')
+ self.assertEqual(move_receipt.move_line_ids.product_uom_qty, 3)
+
def test_unreserve_1(self):
""" Check that unreserving a stock move sets the products reserved as available and
set the state back to confirmed.
diff --git a/addons/web/models/ir_qweb.py b/addons/web/models/ir_qweb.py
index 34d2ecb0d5d..b734c4f193c 100644
--- a/addons/web/models/ir_qweb.py
+++ b/addons/web/models/ir_qweb.py
@@ -65,6 +65,7 @@ class Image(models.AbstractModel):
atts["alt"] = alt
atts["data-zoom"] = src_zoom and u'1' or None
atts["data-zoom-image"] = src_zoom
+ atts["data-no-post-process"] = options.get('data-no-post-process')
atts = self.env['ir.qweb']._post_processing_att('img', atts, options.get('template_options'))
diff --git a/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js b/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
index d0eed176d9c..6fec5795bee 100644
--- a/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
+++ b/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
@@ -103,6 +103,14 @@ odoo.define('web.KeyboardNavigationMixin', function (require) {
* return {undefined|false}
*/
_onKeyDown: function (keyDownEvent) {
+ if ($('body.o_ui_blocked').length &&
+ (keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&
+ !keyDownEvent.ctrlKey) {
+ if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;
+ if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();
+ if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;
+ return false;
+ }
if (!this._areAccessKeyVisible &&
(keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&
!keyDownEvent.ctrlKey) {
diff --git a/addons/web/static/src/js/core/misc.js b/addons/web/static/src/js/core/misc.js
index be0f012752c..cd9a73ef22b 100644
--- a/addons/web/static/src/js/core/misc.js
+++ b/addons/web/static/src/js/core/misc.js
@@ -52,6 +52,29 @@ if ($.blockUI) {
}
+/**
+ * Remove the "accesskey" attributes to avoid the use of the access keys
+ * while the blockUI is enable.
+ */
+
+function blockAccessKeys() {
+ var elementWithAccessKey = [];
+ elementWithAccessKey = document.querySelectorAll('[accesskey]');
+ _.each(elementWithAccessKey, function (elem) {
+ elem.setAttribute("data-accesskey",elem.getAttribute('accesskey'));
+ elem.removeAttribute('accesskey');
+ });
+}
+
+function unblockAccessKeys() {
+ var elementWithDataAccessKey = [];
+ elementWithDataAccessKey = document.querySelectorAll('[data-accesskey]');
+ _.each(elementWithDataAccessKey, function (elem) {
+ elem.setAttribute('accesskey', elem.getAttribute('data-accesskey'));
+ elem.removeAttribute('data-accesskey');
+ });
+}
+
var throbbers = [];
function blockUI() {
@@ -60,6 +83,7 @@ function blockUI() {
throbbers.push(throbber);
throbber.appendTo($(".oe_blockui_spin_container"));
$(document.body).addClass('o_ui_blocked');
+ blockAccessKeys();
return tmp;
}
@@ -67,6 +91,7 @@ function unblockUI() {
_.invoke(throbbers, 'destroy');
throbbers = [];
$(document.body).removeClass('o_ui_blocked');
+ unblockAccessKeys();
return $.unblockUI.apply($, arguments);
}
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 186d0798e8f..c095871b75d 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1006,8 +1006,14 @@ var BasicModel = AbstractModel.extend({
params: params,
})
.then(function () {
+ var offset = options.offset ? options.offset : 0;
+ var old_data = data.data.slice();
data.data = _.sortBy(data.data, function (d) {
- return _.indexOf(resIDs, self.localData[d].res_id);
+ if (_.contains(resIDs, self.localData[d].res_id)) {
+ return _.indexOf(resIDs, self.localData[d].res_id) + offset;
+ } else {
+ return _.indexOf(old_data, d);
+ }
});
data.res_ids = [];
_.each(data.data, function (d) {
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index e801fcc5323..80edb8047ba 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -389,7 +389,7 @@ return AbstractRenderer.extend({
};
}
data_dict[serie].values.push({
- x: tick, y: graphData[i].value,
+ x: tick - 1, y: graphData[i].value,
});
data = _.map(data_dict, identity);
}
diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss
index 1c6fdf3abb1..3681c38eb47 100644
--- a/addons/web/static/src/scss/fields.scss
+++ b/addons/web/static/src/scss/fields.scss
@@ -403,6 +403,14 @@
}
}
+ // PDF Viewer
+ &.o_field_pdfviewer, .o_pdfview_iframe {
+ width: 100%;
+ height: 450px;
+ border: 0;
+ margin-top: 10px;
+ }
+
// Copy to clipboard
&.o_field_copy {
position: relative;
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 6839223a903..f05c89c1a7d 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -882,6 +882,23 @@ var MockServer = Class.extend({
return data;
},
/**
+ * Simulates a 'resequence' operation
+ *
+ * @private
+ * @param {string} model
+ * @param {string} field
+ * @param {Array} ids
+ */
+ _mockResequence: function (args) {
+ var offset = args.offset ? Number(args.offset) : 0;
+ var field = args.field ? args.field : 'sequence';
+ var records = this.data[args.model].records;
+ for (var i in args.ids) {
+ var record = _.findWhere(this.data[args.model].records, {id: args.ids[i]});
+ record[field] = Number(i) + offset;
+ }
+ },
+ /**
* Simulate a 'search_count' operation
*
* @private
@@ -1040,7 +1057,7 @@ var MockServer = Class.extend({
return $.when(this._mockSearchReadController(args));
case '/web/dataset/resequence':
- return $.when();
+ return $.when(this._mockResequence(args));
}
if (route.indexOf('/web/image') >= 0 || _.contains(['.png', '.jpg'], route.substr(route.length - 4))) {
return $.when();
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index 0c3b664afc5..2e74e36e5e1 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -152,6 +152,29 @@ QUnit.module('Views', {
});
});
+ QUnit.test('displaying line chart data with multiple groupbys', function (assert) {
+ // this test makes sure the line chart shows all data labels (X axis) when
+ // it is grouped by several fields
+ assert.expect(3);
+
+ var graph = createView({
+ View: GraphView,
+ model: 'foo',
+ data: this.data,
+ arch: '<graph type="line"><field name="foo" /></graph>',
+ groupBy: ['product_id', 'bar'],
+ });
+
+ assert.strictEqual(graph.$('.nv-x text:contains(xphone)').length, 1,
+ "should contain a text element with product xphone on X axis");
+ assert.strictEqual(graph.$('.nv-x text:contains(xpad)').length, 1,
+ "should contain a text element with product xpad on X axis");
+ assert.strictEqual(graph.$('text:contains(true)').length, 1,
+ "should have an entry for each value of field 'bar' in the legend");
+
+ graph.destroy();
+ });
+
QUnit.test('switching measures', function (assert) {
var done = assert.async();
assert.expect(4);
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index d389e4ef34c..3fb1ca99ded 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -3156,20 +3156,118 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('result of consecutive resequences is correctly sorted', function (assert) {
+ assert.expect(9);
+ this.data = { // we want the data to be minimal to have a minimal test
+ foo: {
+ fields: {int_field: {string: "int_field", type: "integer", sortable: true}},
+ records: [
+ {id: 1, int_field: 0},
+ {id: 2, int_field: 1},
+ {id: 3, int_field: 2},
+ {id: 4, int_field: 3},
+ ]
+ }
+ };
+ var moves = 0;
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree>' +
+ '<field name="int_field" widget="handle"/>' +
+ '<field name="id"/>' +
+ '</tree>',
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/resequence') {
+ if (moves === 0) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [4, 3],
+ offset: 2,
+ field: "int_field",
+ });
+ }
+ if (moves === 1) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [1, 4, 2, 3],
+ field: "int_field",
+ });
+ }
+ if (moves === 2) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [2, 4],
+ offset: 1,
+ field: "int_field",
+ });
+ }
+ if (moves === 3) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [1, 4, 2, 3],
+ field: "int_field",
+ });
+ }
+ moves += 1;
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ assert.strictEqual(list.$('tbody tr td').text(), '1234',
+ "default should be sorted by id");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(3),
+ list.$('tbody tr').eq(2),
+ {position: 'top'}
+ );
+ assert.strictEqual(list.$('tbody tr td').text(), '1243',
+ "the int_field (sequence) should have been correctly updated");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(2),
+ list.$('tbody tr').eq(1),
+ {position: 'top'}
+ );
+ assert.deepEqual(list.$('tbody tr td').text(), '1423',
+ "the int_field (sequence) should have been correctly updated");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(1),
+ list.$('tbody tr').eq(3),
+ {position: 'top'}
+ );
+ assert.deepEqual(list.$('tbody tr td').text(), '1243',
+ "the int_field (sequence) should have been correctly updated");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(2),
+ list.$('tbody tr').eq(1),
+ {position: 'top'}
+ );
+ assert.deepEqual(list.$('tbody tr td').text(), '1423',
+ "the int_field (sequence) should have been correctly updated");
+ list.destroy();
+ });
+
QUnit.test('editable list with handle widget', function (assert) {
assert.expect(12);
+ // resequence makes sense on a sequence field, not on arbitrary fields
+ this.data.foo.records[0].int_field = 0;
+ this.data.foo.records[1].int_field = 1;
+ this.data.foo.records[2].int_field = 2;
+ this.data.foo.records[3].int_field = 3;
+
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="top">' +
+ arch: '<tree editable="top" default_order="int_field">' +
'<field name="int_field" widget="handle"/>' +
'<field name="amount" widget="float" digits="[5,0]"/>' +
'</tree>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
- assert.strictEqual(args.offset, -4,
+ assert.strictEqual(args.offset, 1,
"should write the sequence starting from the lowest current one");
assert.strictEqual(args.field, 'int_field',
"should write the right field as sequence");
@@ -3217,6 +3315,12 @@ QUnit.module('Views', {
QUnit.test('editable list with handle widget with slow network', function (assert) {
assert.expect(15);
+ // resequence makes sense on a sequence field, not on arbitrary fields
+ this.data.foo.records[0].int_field = 0;
+ this.data.foo.records[1].int_field = 1;
+ this.data.foo.records[2].int_field = 2;
+ this.data.foo.records[3].int_field = 3;
+
var def = $.Deferred();
var list = createView({
@@ -3229,7 +3333,7 @@ QUnit.module('Views', {
'</tree>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
- assert.strictEqual(args.offset, -4,
+ assert.strictEqual(args.offset, 1,
"should write the sequence starting from the lowest current one");
assert.strictEqual(args.field, 'int_field',
"should write the right field as sequence");
diff --git a/addons/website_crm_partner_assign/controllers/main.py b/addons/website_crm_partner_assign/controllers/main.py
index 52f48d58b2a..67272576845 100644
--- a/addons/website_crm_partner_assign/controllers/main.py
+++ b/addons/website_crm_partner_assign/controllers/main.py
@@ -235,7 +235,7 @@ class WebsiteCrmPartnerAssign(WebsitePartnerPage):
grade_domain += [('country_id', '=', country.id)]
grades = partner_obj.sudo().read_group(
grade_domain, ["id", "grade_id"],
- groupby="grade_id", orderby="grade_id DESC")
+ groupby="grade_id")
grades_partners = partner_obj.sudo().search_count(grade_domain)
# flag active grade
for grade_dict in grades:
diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py
index 4ab82909c5b..71659aff1f5 100644
--- a/odoo/addons/base/models/ir_http.py
+++ b/odoo/addons/base/models/ir_http.py
@@ -285,7 +285,7 @@ class IrHttp(models.AbstractModel):
obj = cls._xmlid_to_obj(env, xmlid)
elif id and model == 'ir.attachment' and access_token:
obj = env[model].sudo().browse(int(id))
- if not consteq(obj.access_token, access_token):
+ if not consteq(obj.access_token or '', access_token):
return (403, [], None)
elif id and share_id and share_token:
share = env['documents.share'].sudo().browse(int(share_id))
diff --git a/odoo/addons/base/models/ir_qweb.py b/odoo/addons/base/models/ir_qweb.py
index e933ce787b6..5a117583aed 100644
--- a/odoo/addons/base/models/ir_qweb.py
+++ b/odoo/addons/base/models/ir_qweb.py
@@ -351,7 +351,7 @@ class IrQWeb(models.AbstractModel, QWeb):
files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media})
elif can_aggregate(src) and el.tag == 'script':
atype = 'text/javascript'
- path = [segment for segment in href.split('/') if segment]
+ path = [segment for segment in src.split('/') if segment]
filename = get_resource_path(*path) if path else None
files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media})
else:
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index 34bcd3ef837..e966b2172b4 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -306,8 +306,9 @@ class Users(models.Model):
instead.
"""
""" Override this method to plug additional authentication methods"""
+ assert password
self.env.cr.execute(
- 'SELECT password FROM res_users WHERE id=%s',
+ "SELECT COALESCE(password, '') FROM res_users WHERE id=%s",
[self.env.user.id]
)
[hashed] = self.env.cr.fetchone()
diff --git a/requirements.txt b/requirements.txt
index 9a81f2e6b8a..c5c09f800cf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -31,7 +31,8 @@ pyserial==3.1.1
python-dateutil==2.5.3
pytz==2016.7
pyusb==1.0.0
-PyYAML==3.12
+PyYAML==3.12 ; python_version < '3.7'
+PyYAML==3.13 ; python_version >= '3.7'
qrcode==5.3
reportlab==3.3.0
requests==2.11.1