diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py
index 4402cb5a127..e08f37b135e 100644
--- a/addons/delivery/models/sale_order.py
+++ b/addons/delivery/models/sale_order.py
@@ -124,3 +124,7 @@ class SaleOrderLine(models.Model):
if not line.product_id or not line.product_uom or not line.product_uom_qty:
return 0.0
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
+
+ def _is_delivery(self):
+ self.ensure_one()
+ return self.is_delivery
diff --git a/addons/event/security/event_security.xml b/addons/event/security/event_security.xml
index 110262928b6..7664085fc0e 100644
--- a/addons/event/security/event_security.xml
+++ b/addons/event/security/event_security.xml
@@ -41,5 +41,12 @@
]
+
+ Event/Registration: Portal
+
+
+ ['|', ('email', '=', user.partner_id.email), ('partner_id', '=', user.partner_id.id)]
+
+
diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv
index ed7ec0e0cc6..8a49f01f191 100644
--- a/addons/event/security/ir.model.access.csv
+++ b/addons/event/security/ir.model.access.csv
@@ -5,7 +5,8 @@ access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
-access_event_registration_portal,event.registration,model_event_registration,,0,0,0,0
+access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0
+access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0
access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0
diff --git a/addons/event_sale/__manifest__.py b/addons/event_sale/__manifest__.py
index 97140bd471f..2293b52dfb5 100644
--- a/addons/event_sale/__manifest__.py
+++ b/addons/event_sale/__manifest__.py
@@ -26,6 +26,7 @@ this event.
'data/event_sale_data.xml',
'report/event_event_templates.xml',
'security/ir.model.access.csv',
+ 'security/event_security.xml',
'wizard/event_edit_registration.xml',
],
'demo': ['data/event_demo.xml'],
diff --git a/addons/event_sale/security/event_security.xml b/addons/event_sale/security/event_security.xml
new file mode 100644
index 00000000000..a5bc0885b9b
--- /dev/null
+++ b/addons/event_sale/security/event_security.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/addons/hr_payroll/models/hr_salary_rule.py b/addons/hr_payroll/models/hr_salary_rule.py
index af9d6c342a5..f8a342a354b 100644
--- a/addons/hr_payroll/models/hr_salary_rule.py
+++ b/addons/hr_payroll/models/hr_salary_rule.py
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
-from odoo.exceptions import UserError
+from odoo.exceptions import UserError, ValidationError
from odoo.tools.safe_eval import safe_eval
from odoo.addons import decimal_precision as dp
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 6a6735f3d66..f3aeddf85a7 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -5,6 +5,7 @@ import logging
import re
from email.utils import formataddr
+from openerp.http import request
from odoo import _, api, fields, models, modules, SUPERUSER_ID, tools
from odoo.exceptions import UserError, AccessError
@@ -288,11 +289,12 @@ class Message(models.Model):
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
attachments_data = attachments.sudo().read(['id', 'datas_fname', 'name', 'mimetype'])
+ safari = request and request.httprequest.user_agent.browser == 'safari'
attachments_tree = dict((attachment['id'], {
'id': attachment['id'],
'filename': attachment['datas_fname'],
'name': attachment['name'],
- 'mimetype': attachment['mimetype'],
+ 'mimetype': 'application/octet-stream' if safari and 'video' in attachment['mimetype'] else attachment['mimetype'],
}) for attachment in attachments_data)
# 3. Tracking values
diff --git a/addons/mass_mailing_event/views/event_views.xml b/addons/mass_mailing_event/views/event_views.xml
index e3105fad8eb..480c0dddf21 100644
--- a/addons/mass_mailing_event/views/event_views.xml
+++ b/addons/mass_mailing_event/views/event_views.xml
@@ -10,7 +10,9 @@
name="action_mass_mailing_attendees"
icon="fa-envelope-o"
attrs="{'invisible': [('seats_expected', '=', 0)]}">
- Mail Attendees
+
+ Mail Attendees
+
diff --git a/addons/mrp/report/mrp_bom_cost_report_templates.xml b/addons/mrp/report/mrp_bom_cost_report_templates.xml
index 85d3b442a93..225c3dca5d8 100644
--- a/addons/mrp/report/mrp_bom_cost_report_templates.xml
+++ b/addons/mrp/report/mrp_bom_cost_report_templates.xml
@@ -36,7 +36,7 @@
-
+
|
diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py
index 2ef110a6dae..44ebafa3242 100644
--- a/addons/point_of_sale/models/pos_config.py
+++ b/addons/point_of_sale/models/pos_config.py
@@ -332,10 +332,11 @@ class PosConfig(models.Model):
def name_get(self):
result = []
for config in self:
- if (not config.session_ids) or (config.session_ids[0].state == 'closed'):
+ last_session = self.env['pos.session'].search([('config_id', '=', config.id)], limit=1)
+ if (not last_session) or (last_session.state == 'closed'):
result.append((config.id, config.name + ' (' + _('not used') + ')'))
continue
- result.append((config.id, config.name + ' (' + config.session_ids[0].user_id.name + ')'))
+ result.append((config.id, config.name + ' (' + last_session.user_id.name + ')'))
return result
@api.model
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
index f06abc15c66..f45a48319ba 100644
--- a/addons/point_of_sale/static/src/js/devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -215,7 +215,7 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
var self = this;
function status(){
- self.connection.rpc('/hw_proxy/status_json',{},{timeout:2500})
+ self.connection.rpc('/hw_proxy/status_json',{},{shadow: true, timeout:2500})
.then(function(driver_status){
self.set_connection_status('connected',driver_status);
},function(){
@@ -239,7 +239,7 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
callbacks[i](params);
}
if(this.get('status').status !== 'disconnected'){
- return this.connection.rpc('/hw_proxy/' + name, params || {});
+ return this.connection.rpc('/hw_proxy/' + name, params || {}, {shadow: true});
}else{
return (new $.Deferred()).reject();
}
@@ -588,7 +588,7 @@ var BarcodeReader = core.Class.extend({
this.remote_active = 1;
function waitforbarcode(){
- return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500})
+ return self.proxy.connection.rpc('/hw_proxy/scanner',{},{shadow: true, timeout:7500})
.then(function(barcode){
if(!self.remote_scanning){
self.remote_active = 0;
diff --git a/addons/point_of_sale/views/point_of_sale_dashboard.xml b/addons/point_of_sale/views/point_of_sale_dashboard.xml
index 8b70e02c9ec..49f4da04fe2 100644
--- a/addons/point_of_sale/views/point_of_sale_dashboard.xml
+++ b/addons/point_of_sale/views/point_of_sale_dashboard.xml
@@ -141,7 +141,7 @@
-
+
diff --git a/addons/pos_restaurant/static/src/js/multiprint.js b/addons/pos_restaurant/static/src/js/multiprint.js
index 1a135bdfe03..738b13d9362 100644
--- a/addons/pos_restaurant/static/src/js/multiprint.js
+++ b/addons/pos_restaurant/static/src/js/multiprint.js
@@ -27,11 +27,14 @@ var Printer = core.Class.extend(mixins.PropertiesMixin,{
function send_printing_job(){
if(self.receipt_queue.length > 0){
var r = self.receipt_queue.shift();
- self.connection.rpc('/hw_proxy/print_xml_receipt',{receipt: r},{timeout: 5000})
+ var options = {shadow: true, timeout: 5000};
+ self.connection.rpc('/hw_proxy/print_xml_receipt', {receipt: r}, options)
.then(function(){
send_printing_job();
- },function(){
+ },function(error, event){
self.receipt_queue.unshift(r);
+ console.log('There was an error while trying to print the order:');
+ console.log(error);
});
}
}
@@ -55,7 +58,7 @@ models.load_models({
for(var i = 0; i < printers.length; i++){
if(active_printers[printers[i].id]){
- var url = printers[i].proxy_ip;
+ var url = printers[i].proxy_ip || '';
if(url.indexOf('//') < 0){
url = 'http://'+url;
}
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 70fad5e6152..40077d86acc 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -964,12 +964,12 @@ class Task(models.Model):
@api.multi
def message_get_suggested_recipients(self):
recipients = super(Task, self).message_get_suggested_recipients()
- for task in self.filtered('partner_id'):
- reason = _('Customer Email') if task.partner_id.email else _('Customer')
+ for task in self:
if task.partner_id:
+ reason = _('Customer Email') if task.partner_id.email else _('Customer')
task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
elif task.email_from:
- task._message_add_suggested_recipient(recipients, partner=task.email_from, reason=reason)
+ task._message_add_suggested_recipient(recipients, email=task.email_from, reason=_('Customer Email'))
return recipients
@api.multi
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 397952619d1..789b37b95d3 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -402,6 +402,8 @@ class PurchaseOrder(models.Model):
for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
pick.action_cancel()
+ order.order_line.write({'move_dest_ids':[(5,0,0)]})
+
self.write({'state': 'cancel'})
@api.multi
@@ -947,7 +949,8 @@ class ProcurementRule(models.Model):
cache[domain] = po
if not po:
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
- po = self.env['purchase.order'].sudo().create(vals)
+ company_id = values.get('company_id') and values['company_id'].id or self.env.user.company_id.id
+ po = self.env['purchase.order'].with_context(force_company=company_id).sudo().create(vals)
cache[domain] = po
elif not po.origin or origin not in po.origin.split(', '):
if po.origin:
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 657dccf4c63..23d1e4dccf0 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -1239,3 +1239,8 @@ class SaleOrderLine(models.Model):
discount = (new_list_price - price) / new_list_price * 100
if discount > 0:
self.discount = discount
+
+
+ def _is_delivery(self):
+ self.ensure_one()
+ return False
\ No newline at end of file
diff --git a/addons/sale/views/res_config_settings_views.xml b/addons/sale/views/res_config_settings_views.xml
index ed515fe7b45..002a57eb8ab 100644
--- a/addons/sale/views/res_config_settings_views.xml
+++ b/addons/sale/views/res_config_settings_views.xml
@@ -347,7 +347,7 @@
Invoicing
-
+
diff --git a/addons/sale_management/static/src/js/tour.js b/addons/sale_management/static/src/js/tour.js
index 89c792e104a..b379a0526d3 100644
--- a/addons/sale_management/static/src/js/tour.js
+++ b/addons/sale_management/static/src/js/tour.js
@@ -48,6 +48,8 @@ tour.register('sale_tour', {
in_modal: false,
run: function (actions) {
actions.auto();
+ // There might be a modal because of the view:
+ // sale.order.form.editable.list, enabled by some groups
if ($(".modal-footer .btn-primary").length) {
actions.auto(".modal-footer .btn-primary");
}
diff --git a/addons/sale_order_dates/models/sale_order.py b/addons/sale_order_dates/models/sale_order.py
index 0a01683040d..d206e4c8f3d 100644
--- a/addons/sale_order_dates/models/sale_order.py
+++ b/addons/sale_order_dates/models/sale_order.py
@@ -33,7 +33,7 @@ class SaleOrder(models.Model):
for order in self:
dates_list = []
order_datetime = fields.Datetime.from_string(order.date_order)
- for line in order.order_line.filtered(lambda x: x.state != 'cancel'):
+ for line in order.order_line.filtered(lambda x: x.state != 'cancel' and not x._is_delivery()):
dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
dates_list.append(dt)
if dates_list:
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 39958e6e616..592acae3de3 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -585,3 +585,23 @@ class ProductCategory(models.Model):
category = category.parent_id
routes |= category.route_ids
self.total_route_ids = routes
+
+
+class ProductUoM(models.Model):
+ _inherit = 'product.uom'
+
+ def write(self, values):
+ # Users can not update the factor if open stock moves are based on it
+ if 'factor' in values or 'factor_inv' in values or 'category_id' in values:
+ stock_move_lines = self.env['stock.move.line'].search_count([
+ ('product_uom_id.category_id', '=', self.category_id.id),
+ ('state', '!=', 'cancel'),
+ ])
+
+ if stock_move_lines:
+ raise UserError(_(
+ "You cannot change the ratio of this unit of mesure as some"
+ " products with this UoM have already been moved or are "
+ "currently reserved."
+ ))
+ return super(ProductUoM, self).write(values)
diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py
index 05e4bb2b60d..5a06cc2425e 100644
--- a/addons/stock/models/stock_location.py
+++ b/addons/stock/models/stock_location.py
@@ -83,6 +83,21 @@ class Location(models.Model):
if 'usage' in values and values['usage'] == 'view':
if self.mapped('quant_ids'):
raise UserError(_("This location's usage cannot be changed to view as it contains products."))
+ if 'usage' in values or 'scrap_location' in values:
+ modified_locations = self.filtered(
+ lambda l: l.usage != values.get('usage') or
+ l.scrap_location != values.get('scrap_location')
+ )
+ reserved_quantities = self.env['stock.move.line'].search_count([
+ ('location_id', 'in', modified_locations.ids),
+ ('product_qty', '>', 0),
+ ])
+ if reserved_quantities:
+ raise UserError(_(
+ "You cannot change the location type or its use as a scrap"
+ " location as there are products reserved in this location."
+ " Please unreserve the products first."
+ ))
return super(Location, self).write(values)
def name_get(self):
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 53111d925fe..1feda768342 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -787,7 +787,7 @@ class StockMove(models.Model):
group_id = False
return {
'company_id': self.company_id,
- 'date_planned': self.date,
+ 'date_planned': self.date_expected,
'move_dest_ids': self,
'group_id': group_id,
'route_ids': self.route_ids,
diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py
index 0b73b7815e6..0439e8d7ccb 100644
--- a/addons/stock/models/stock_quant.py
+++ b/addons/stock/models/stock_quant.py
@@ -439,6 +439,12 @@ class QuantPackage(models.Model):
if move_lines_to_remove:
move_lines_to_remove.write({'result_package_id': False})
else:
+ move_line_to_modify = self.env['stock.move.line'].search([
+ ('package_id', '=', package.id),
+ ('state', 'in', ('assigned', 'partially_available')),
+ ('product_qty', '!=', 0),
+ ])
+ move_line_to_modify.write({'package_id': False})
package.mapped('quant_ids').write({'package_id': False})
def action_view_picking(self):
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 661628b2132..07916d95fa6 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -397,7 +397,7 @@ class Warehouse(models.Model):
pull_rules_list = supplier_wh._get_supply_pull_rules_values(
[self.Routing(output_location, transit_location, supplier_wh.out_type_id)],
- values={'route_id': inter_wh_route.id, 'propagate_warehouse_id': self.id})
+ values={'route_id': inter_wh_route.id})
pull_rules_list += self._get_supply_pull_rules_values(
[self.Routing(transit_location, input_location, self.in_type_id)],
values={'route_id': inter_wh_route.id, 'propagate_warehouse_id': supplier_wh.id})
diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py
index 52f5e2c53b0..25ae6888c7c 100644
--- a/addons/stock/tests/__init__.py
+++ b/addons/stock/tests/__init__.py
@@ -9,6 +9,7 @@ from . import test_quant
from . import test_inventory
from . import test_move
from . import test_move2
+from . import test_robustness
from . import test_packing
from . import test_packing_neg
from . import test_proc_rule
diff --git a/addons/stock/tests/test_robustness.py b/addons/stock/tests/test_robustness.py
new file mode 100644
index 00000000000..bd3dc6db96e
--- /dev/null
+++ b/addons/stock/tests/test_robustness.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.exceptions import UserError
+from odoo.tests.common import TransactionCase
+
+
+class TestRobustness(TransactionCase):
+ def setUp(self):
+ super(TestRobustness, self).setUp()
+ self.stock_location = self.env.ref('stock.stock_location_stock')
+ self.customer_location = self.env.ref('stock.stock_location_customers')
+ self.uom_unit = self.env.ref('product.product_uom_unit')
+ self.uom_dozen = self.env.ref('product.product_uom_dozen')
+ self.product1 = self.env['product.product'].create({
+ 'name': 'Product A',
+ 'type': 'product',
+ 'categ_id': self.env.ref('product.product_category_all').id,
+ })
+
+ def test_uom_factor(self):
+ """ Changing the factor of a unit of measure shouldn't be allowed while
+ quantities are reserved, else the existing move lines won't be consistent
+ with the `reserved_quantity` on quants.
+ """
+ # make some stock
+ self.env['stock.quant']._update_available_quantity(
+ self.product1,
+ self.stock_location,
+ 12,
+ )
+
+ # reserve a dozen
+ move1 = self.env['stock.move'].create({
+ 'name': 'test_uom_rounding',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': self.customer_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_dozen.id,
+ 'product_uom_qty': 1,
+ })
+ move1._action_confirm()
+ move1._action_assign()
+ self.assertEqual(move1.state, 'assigned')
+ quant = self.env['stock.quant']._gather(
+ self.product1,
+ self.stock_location,
+ )
+
+ # assert the reservation
+ self.assertEqual(quant.reserved_quantity, 12)
+ self.assertEqual(move1.product_qty, 12)
+
+ # change the factor
+ with self.assertRaises(UserError):
+ with self.cr.savepoint():
+ move1.product_uom.factor = 0.05
+
+ # assert the reservation
+ self.assertEqual(quant.reserved_quantity, 12)
+ self.assertEqual(move1.state, 'assigned')
+ self.assertEqual(move1.product_qty, 12)
+
+ # unreserve
+ move1._do_unreserve()
+
+ def test_location_usage(self):
+ """ Changing the usage of a location shouldn't be allowed while
+ quantities are reserved, else the existing move lines won't be
+ consistent with the `reserved_quantity` on the quants.
+ """
+ # change stock usage
+ self.stock_location.scrap_location = True
+
+ # make some stock
+ self.env['stock.quant']._update_available_quantity(
+ self.product1,
+ self.stock_location,
+ 1,
+ )
+
+ # reserve a unit
+ move1 = self.env['stock.move'].create({
+ 'name': 'test_location_archive',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': self.customer_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 1,
+ })
+ move1._action_confirm()
+ move1._action_assign()
+ self.assertEqual(move1.state, 'assigned')
+ quant = self.env['stock.quant']._gather(
+ self.product1,
+ self.stock_location,
+ )
+
+ # assert the reservation
+ self.assertEqual(quant.reserved_quantity, 0) # reservation is bypassed in scrap location
+ self.assertEqual(move1.product_qty, 1)
+
+ # change the stock usage
+ with self.assertRaises(UserError):
+ with self.cr.savepoint():
+ self.stock_location.scrap_location = False
+
+ # unreserve
+ move1._do_unreserve()
+
+ def test_package_unpack(self):
+ """ Unpack a package that contains quants with a reservation
+ should also remove the package on the reserved move lines.
+ """
+ package = self.env['stock.quant.package'].create({
+ 'name': 'Shell Helix HX7 10W30',
+ })
+
+ self.env['stock.quant']._update_available_quantity(
+ self.product1,
+ self.stock_location,
+ 10,
+ package_id=package
+ )
+
+ # reserve a dozen
+ move1 = self.env['stock.move'].create({
+ 'name': 'test_uom_rounding',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': self.customer_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 10,
+ })
+ move1._action_confirm()
+ move1._action_assign()
+
+ move1.result_package_id = False
+
+ package.unpack()
+
+ # unreserve
+ move1._do_unreserve()
+ self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1)
+ self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, package_id=package)), 0)
+
+ self.assertEqual(self.env['stock.quant']._gather(self.product1, self.stock_location).reserved_quantity, 0)
diff --git a/addons/stock/tests/test_warehouse.py b/addons/stock/tests/test_warehouse.py
index e793b323b98..b79a5ab768c 100644
--- a/addons/stock/tests/test_warehouse.py
+++ b/addons/stock/tests/test_warehouse.py
@@ -228,6 +228,215 @@ class TestWarehouse(TestStockCommon):
quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', location_loss.id)])
self.assertEqual(len(quant), 1)
+ def test_resupply_route(self):
+ """ Simulate a resupply chain between warehouses.
+ Stock -> transit -> Dist. -> transit -> Shop -> Customer
+ Create the move from Shop to Customer and ensure that all the pull
+ rules are triggered in order to complete the move chain to Stock.
+ """
+ warehouse_stock = self.env['stock.warehouse'].create({
+ 'name': 'Stock.',
+ 'code': 'STK',
+ })
+
+ warehouse_distribution = self.env['stock.warehouse'].create({
+ 'name': 'Dist.',
+ 'code': 'DIST',
+ 'default_resupply_wh_id': warehouse_stock.id,
+ 'resupply_wh_ids': [(6, 0, [warehouse_stock.id])]
+ })
+
+ warehouse_shop = self.env['stock.warehouse'].create({
+ 'name': 'Shop',
+ 'code': 'SHOP',
+ 'default_resupply_wh_id': warehouse_distribution.id,
+ 'resupply_wh_ids': [(6, 0, [warehouse_distribution.id])]
+ })
+
+ route_stock_to_dist = warehouse_distribution.resupply_route_ids
+ route_dist_to_shop = warehouse_shop.resupply_route_ids
+
+ # Change the procure_method on the pull rules between dist and shop
+ # warehouses. Since mto and resupply routes are both on product it will
+ # select one randomly between them and if it select the resupply it is
+ # 'make to stock' and it will not create the picking between stock and
+ # dist warehouses.
+ route_dist_to_shop.pull_ids.write({'procure_method': 'make_to_order'})
+
+ product = self.env['product.product'].create({
+ 'name': 'Fakir',
+ 'type': 'product',
+ 'route_ids': [(4, route_id) for route_id in [route_stock_to_dist.id, route_dist_to_shop.id, self.env.ref('stock.route_warehouse0_mto').id]],
+ })
+
+ picking_out = self.env['stock.picking'].create({
+ 'partner_id': self.env.ref('base.res_partner_2').id,
+ 'picking_type_id': self.env.ref('stock.picking_type_out').id,
+ 'location_id': warehouse_shop.lot_stock_id.id,
+ 'location_dest_id': self.env.ref('stock.stock_location_customers').id,
+ })
+ self.env['stock.move'].create({
+ 'name': product.name,
+ 'product_id': product.id,
+ 'product_uom_qty': 1,
+ 'product_uom': product.uom_id.id,
+ 'picking_id': picking_out.id,
+ 'location_id': warehouse_shop.lot_stock_id.id,
+ 'location_dest_id': self.env.ref('stock.stock_location_customers').id,
+ 'warehouse_id': warehouse_shop.id,
+ 'procure_method': 'make_to_order',
+ })
+ picking_out.action_confirm()
+
+ moves = self.env['stock.move'].search([('product_id', '=', product.id)])
+ # Shop/Stock -> Customer
+ # Transit -> Shop/Stock
+ # Dist/Stock -> Transit
+ # Transit -> Dist/Stock
+ # Stock/Stock -> Transit
+ self.assertEqual(len(moves), 5, 'Invalid moves number.')
+ self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_stock.lot_stock_id.id)]))
+ self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_distribution.lot_stock_id.id)]))
+ self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_distribution.lot_stock_id.id)]))
+ self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_shop.lot_stock_id.id)]))
+ self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_shop.lot_stock_id.id)]))
+
+ def test_mutiple_resupply_warehouse(self):
+ """ Simulate the following situation:
+ - 2 shops with stock are resupply by 2 distinct warehouses
+ - Shop Namur is resupply by the warehouse stock Namur
+ - Shop Wavre is resupply by the warehouse stock Wavre
+ - Simulate 2 moves for the same product but in different shop.
+ This test ensure that the move are supplied by the correct distribution
+ warehouse.
+ """
+ customer_location = self.env.ref('stock.stock_location_customers')
+
+ warehouse_distribution_wavre = self.env['stock.warehouse'].create({
+ 'name': 'Stock Wavre.',
+ 'code': 'WV',
+ })
+
+ warehouse_shop_wavre = self.env['stock.warehouse'].create({
+ 'name': 'Shop Wavre',
+ 'code': 'SHWV',
+ 'default_resupply_wh_id': warehouse_distribution_wavre.id,
+ 'resupply_wh_ids': [(6, 0, [warehouse_distribution_wavre.id])]
+ })
+
+ warehouse_distribution_namur = self.env['stock.warehouse'].create({
+ 'name': 'Stock Namur.',
+ 'code': 'NM',
+ })
+
+ warehouse_shop_namur = self.env['stock.warehouse'].create({
+ 'name': 'Shop Namur',
+ 'code': 'SHNM',
+ 'default_resupply_wh_id': warehouse_distribution_namur.id,
+ 'resupply_wh_ids': [(6, 0, [warehouse_distribution_namur.id])]
+ })
+
+ route_shop_namur = warehouse_shop_namur.resupply_route_ids
+ route_shop_wavre = warehouse_shop_wavre.resupply_route_ids
+
+ # The product contains the 2 resupply routes.
+ product = self.env['product.product'].create({
+ 'name': 'Fakir',
+ 'type': 'product',
+ 'route_ids': [(4, route_id) for route_id in [route_shop_namur.id, route_shop_wavre.id, self.env.ref('stock.route_warehouse0_mto').id]],
+ })
+
+ # Add 1 quant in each distribution warehouse.
+ self.env['stock.quant']._update_available_quantity(product, warehouse_distribution_wavre.lot_stock_id, 1.0)
+ self.env['stock.quant']._update_available_quantity(product, warehouse_distribution_namur.lot_stock_id, 1.0)
+
+ # Create the move for the shop Namur. Should create a resupply from
+ # distribution warehouse Namur.
+ picking_out_namur = self.env['stock.picking'].create({
+ 'partner_id': self.env.ref('base.res_partner_2').id,
+ 'picking_type_id': self.env.ref('stock.picking_type_out').id,
+ 'location_id': warehouse_shop_namur.lot_stock_id.id,
+ 'location_dest_id': customer_location.id,
+ })
+ self.env['stock.move'].create({
+ 'name': product.name,
+ 'product_id': product.id,
+ 'product_uom_qty': 1,
+ 'product_uom': product.uom_id.id,
+ 'picking_id': picking_out_namur.id,
+ 'location_id': warehouse_shop_namur.lot_stock_id.id,
+ 'location_dest_id': customer_location.id,
+ 'warehouse_id': warehouse_shop_namur.id,
+ 'procure_method': 'make_to_order',
+ })
+ picking_out_namur.action_confirm()
+
+ # Validate the picking
+ # Dist. warehouse Namur -> transit Location -> Shop Namur
+ picking_stock_transit = self.env['stock.picking'].search([('location_id', '=', warehouse_distribution_namur.lot_stock_id.id)])
+ self.assertTrue(picking_stock_transit)
+ picking_stock_transit.action_assign()
+ picking_stock_transit.move_lines[0].quantity_done = 1.0
+ picking_stock_transit.action_done()
+
+ picking_transit_shop_namur = self.env['stock.picking'].search([('location_dest_id', '=', warehouse_shop_namur.lot_stock_id.id)])
+ self.assertTrue(picking_transit_shop_namur)
+ picking_transit_shop_namur.action_assign()
+ picking_transit_shop_namur.move_lines[0].quantity_done = 1.0
+ picking_transit_shop_namur.action_done()
+
+ picking_out_namur.action_assign()
+ picking_out_namur.move_lines[0].quantity_done = 1.0
+ picking_out_namur.action_done()
+
+ # Check that the correct quantity has been provided to customer
+ self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 1)
+ # Ensure there still no quants in distribution warehouse
+ self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_namur.lot_stock_id)), 0)
+
+ # Create the move for the shop Wavre. Should create a resupply from
+ # distribution warehouse Wavre.
+ picking_out_wavre = self.env['stock.picking'].create({
+ 'partner_id': self.env.ref('base.res_partner_2').id,
+ 'picking_type_id': self.env.ref('stock.picking_type_out').id,
+ 'location_id': warehouse_shop_wavre.lot_stock_id.id,
+ 'location_dest_id': customer_location.id,
+ })
+ self.env['stock.move'].create({
+ 'name': product.name,
+ 'product_id': product.id,
+ 'product_uom_qty': 1,
+ 'product_uom': product.uom_id.id,
+ 'picking_id': picking_out_wavre.id,
+ 'location_id': warehouse_shop_wavre.lot_stock_id.id,
+ 'location_dest_id': customer_location.id,
+ 'warehouse_id': warehouse_shop_wavre.id,
+ 'procure_method': 'make_to_order',
+ })
+ picking_out_wavre.action_confirm()
+
+ # Validate the picking
+ # Dist. warehouse Wavre -> transit Location -> Shop Wavre
+ picking_stock_transit = self.env['stock.picking'].search([('location_id', '=', warehouse_distribution_wavre.lot_stock_id.id)])
+ self.assertTrue(picking_stock_transit)
+ picking_stock_transit.action_assign()
+ picking_stock_transit.move_lines[0].quantity_done = 1.0
+ picking_stock_transit.action_done()
+
+ picking_transit_shop_wavre = self.env['stock.picking'].search([('location_dest_id', '=', warehouse_shop_wavre.lot_stock_id.id)])
+ self.assertTrue(picking_transit_shop_wavre)
+ picking_transit_shop_wavre.action_assign()
+ picking_transit_shop_wavre.move_lines[0].quantity_done = 1.0
+ picking_transit_shop_wavre.action_done()
+
+ picking_out_wavre.action_assign()
+ picking_out_wavre.move_lines[0].quantity_done = 1.0
+ picking_out_wavre.action_done()
+
+ # Check that the correct quantity has been provided to customer
+ self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 2)
+ # Ensure there still no quants in distribution warehouse
+ self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_wavre.lot_stock_id)), 0)
class TestResupply(TestStockCommon):
def setUp(self):
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py
index 27630eb1011..6e3189ed168 100644
--- a/addons/stock_account/models/product.py
+++ b/addons/stock_account/models/product.py
@@ -146,11 +146,13 @@ class ProductProduct(models.Model):
'account_id': debit_account_id,
'debit': abs(diff * qty_available),
'credit': 0,
+ 'product_id': product.id,
}), (0, 0, {
'name': _('Standard Price changed - %s') % (product.display_name),
'account_id': credit_account_id,
'debit': 0,
'credit': abs(diff * qty_available),
+ 'product_id': product.id,
})],
}
move = AccountMove.create(move_vals)
diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js
index ccc7e089e65..d903b5870c5 100644
--- a/addons/web/static/src/js/services/crash_manager.js
+++ b/addons/web/static/src/js/services/crash_manager.js
@@ -41,7 +41,8 @@ var CrashManager = core.Class.extend({
core.bus.trigger('connection_lost');
this.connection_lost = true;
var timeinterval = setInterval(function() {
- ajax.jsonRpc('/web/webclient/version_info').then(function() {
+ var options = {shadow: true};
+ ajax.jsonRpc('/web/webclient/version_info', 'call', {}, options).then(function () {
clearInterval(timeinterval);
core.bus.trigger('connection_restored');
self.connection_lost = false;
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 8c6b6308609..919bb179c2c 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -2579,6 +2579,11 @@ var BasicModel = AbstractModel.extend({
relationField: field.relation_field,
viewType: view ? view.type : fieldInfo.viewType,
});
+ // set existing changes to the list
+ if (record._changes && record._changes[fieldName]) {
+ list._changes = self.localData[record._changes[fieldName]]._changes;
+ record._changes[fieldName] = list.id;
+ }
record.data[fieldName] = list.id;
if (!fieldInfo.__no_fetch) {
var def = self._readUngroupedList(list).then(function () {
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 0d8e5dc56d4..f98ac1724d7 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -143,10 +143,12 @@ QUnit.module('relational_fields', {
user: {
fields: {
name: {string: "Name", type: "char"},
+ partner_ids: {string: "one2many partners field", type: "one2many", relation: 'partner', relation_field: 'user_id'},
},
records: [{
id: 17,
name: "Aline",
+ partner_ids: [1, 2],
}, {
id: 19,
name: "Christine",
@@ -9474,6 +9476,94 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('onchange affecting inline unopened list view', function (assert) {
+ // when we got onchange result for fields of record that were not
+ // already available because they were in a inline view not already
+ // opened, in a given configuration the change were applied ignoring
+ // existing data, thus a line of a one2many field inside a one2many
+ // field could be duplicated unexplectedly
+ assert.expect(5);
+
+ var numUserOnchange = 0;
+
+ this.data.user.onchanges = {
+ partner_ids: function (obj) {
+ if (numUserOnchange === 0) {
+ // simulate proper server onchange after save of modal with new record
+ obj.partner_ids = [
+ [5],
+ [1, 1, {
+ display_name: 'first record',
+ turtles: [
+ [5],
+ [1, 2, {'display_name': 'donatello'}],
+ ],
+ }],
+ [1, 2, {
+ display_name: 'second record',
+ turtles: [
+ [5],
+ obj.partner_ids[1][2].turtles[0],
+ ],
+ }],
+ ];
+ }
+ numUserOnchange++;
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'user',
+ data: this.data,
+ arch: ' ',
+ res_id: 17,
+ });
+
+ // add a turtle on second partner
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_data_row:eq(1)').click();
+ $('.modal .o_field_x2many_list_row_add a').click();
+ $('.modal input[name="display_name"]').val('michelangelo').change();
+ $('.modal .btn-primary').click();
+ // open first partner so changes from previous action are applied
+ form.$('.o_data_row:eq(0)').click();
+ $('.modal .btn-primary').click();
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(numUserOnchange, 2,
+ 'there should 2 and only 2 onchange from closing the partner modal');
+
+ form.$('.o_data_row:eq(0)').click();
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ 'only 1 turtle for first partner');
+ assert.strictEqual($('.modal .o_data_row').text(), 'donatello',
+ 'first partner turtle is donatello');
+ $('.modal .o_form_button_cancel').click();
+
+ form.$('.o_data_row:eq(1)').click();
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ 'only 1 turtle for second partner');
+ assert.strictEqual($('.modal .o_data_row').text(), 'michelangelo',
+ 'second partner turtle is michelangelo');
+ $('.modal .o_form_button_cancel').click();
+
+ form.destroy();
+ });
+
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {
diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py
index cc25b86d862..0aa3dd24753 100644
--- a/addons/web_editor/controllers/main.py
+++ b/addons/web_editor/controllers/main.py
@@ -253,7 +253,7 @@ class Web_Editor(http.Controller):
## @param bundles - True if the bundles views must be fetched (default to False)
## @param bundles_restriction - Names of the bundle in which to look for less files (if empty, search in all of them)
## @returns a dictionary with views info in the views key and style info in the less key
- @http.route("/web_editor/get_assets_editor_resources", type="json", auth="user")
+ @http.route("/web_editor/get_assets_editor_resources", type="json", auth="user", website=True)
def get_assets_editor_resources(self, key, get_views=True, get_less=True, bundles=False, bundles_restriction=[]):
# Related views must be fetched if the user wants the views and/or the style
views = request.env["ir.ui.view"].get_related_views(key, bundles=bundles)
diff --git a/addons/web_editor/static/src/js/editor/transcoder.js b/addons/web_editor/static/src/js/editor/transcoder.js
index ebe6fea5dbb..84fe3cf8c93 100644
--- a/addons/web_editor/static/src/js/editor/transcoder.js
+++ b/addons/web_editor/static/src/js/editor/transcoder.js
@@ -108,9 +108,14 @@ function getMatchedCSSRules(a) {
// The css generates all the attributes separately and not in simplified form.
// In order to have a better compatibility (outlook for example) we simplify the css tags.
// e.g. border-left-style: none; border-bottom-s .... will be simplified in border-style = none
- _.each([['margin'], ['padding'], ['border', 'style']], function (attr) {
- var p = attr[0];
- var e = attr[1] ? '-' + attr[1] : '';
+ _.each([
+ {property: 'margin'},
+ {property: 'padding'},
+ {property: 'border', propertyEnd: '-style', defaultValue: 'none'},
+ ], function (propertyInfo) {
+ var p = propertyInfo.property;
+ var e = propertyInfo.propertyEnd || '';
+ var defVal = propertyInfo.defaultValue || 0;
if (style[p+'-top'+e] || style[p+'-right'+e] || style[p+'-bottom'+e] || style[p+'-left'+e]) {
if (style[p+'-top'+e] === style[p+'-right'+e] && style[p+'-top'+e] === style[p+'-bottom'+e] && style[p+'-top'+e] === style[p+'-left'+e]) {
@@ -119,7 +124,7 @@ function getMatchedCSSRules(a) {
}
else {
// keep => property: [top value] [right value] [bottom value] [left value];
- style[p+e] = (style[p+'-top'+e] || 0) + ' ' + (style[p+'-right'+e] || 0) + ' ' + (style[p+'-bottom'+e] || 0) + ' ' + (style[p+'-left'+e] || 0);
+ style[p+e] = (style[p+'-top'+e] || defVal) + ' ' + (style[p+'-right'+e] || defVal) + ' ' + (style[p+'-bottom'+e] || defVal) + ' ' + (style[p+'-left'+e] || defVal);
if (style[p+e].indexOf('inherit') !== -1 || style[p+e].indexOf('initial') !== -1) {
// keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
delete style[p+e];
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index f3a8041d54e..a4fb8659326 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -175,7 +175,7 @@ class WebsiteEventController(http.Controller):
'event': event,
'main_object': event,
'range': range,
- 'registrable': event._is_event_registrable()
+ 'registrable': event.sudo()._is_event_registrable()
}
return request.render("website_event.event_description_full", values)
diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml
index 9ae221bc2fd..752d227620e 100644
--- a/addons/website_event/views/event_templates.xml
+++ b/addons/website_event/views/event_templates.xml
@@ -480,7 +480,7 @@
Event registration is closed.
-
+
Configure and Launch Event Registration
diff --git a/addons/website_mail_channel/static/src/js/website_mail_channel.snippet.js b/addons/website_mail_channel/static/src/js/website_mail_channel.snippet.js
index 9daba4bf666..e48cae79df4 100644
--- a/addons/website_mail_channel/static/src/js/website_mail_channel.snippet.js
+++ b/addons/website_mail_channel/static/src/js/website_mail_channel.snippet.js
@@ -8,6 +8,7 @@ sAnimation.registry.follow_alias = sAnimation.Class.extend({
start: function () {
var self = this;
this.is_user = false;
+ var unsubscribePage = window.location.search.slice(1).split('&').indexOf("unsubscribe") >= 0;
this._rpc({
route: '/groups/is_member',
params: {
@@ -19,6 +20,9 @@ sAnimation.registry.follow_alias = sAnimation.Class.extend({
self.is_user = data.is_user;
self.email = data.email;
self.$target.find('.js_mg_link').attr('href', '/groups/' + self.$target.data('id'));
+ if (unsubscribePage && self.is_user) {
+ self.$target.find(".js_mg_follow_form").remove();
+ }
self.toggle_subscription(data.is_member ? 'on' : 'off', data.email);
self.$target.removeClass("hidden");
});
diff --git a/addons/website_sale/models/res_config_settings.py b/addons/website_sale/models/res_config_settings.py
index 91dbebf9f2f..cae080f20aa 100644
--- a/addons/website_sale/models/res_config_settings.py
+++ b/addons/website_sale/models/res_config_settings.py
@@ -43,7 +43,7 @@ class ResConfigSettings(models.TransientModel):
order_mail_template = fields.Many2one('mail.template', string='Order Confirmation Email',
default=_default_order_mail_template, domain="[('model', '=', 'sale.order')]",
- help="Email sent to customer at the end of the checkout process")
+ help="Email sent to customer at the end of the checkout process", readonly=True)
automatic_invoice = fields.Boolean("Automatic Invoice",
help="The invoice is generated automatically and available in the customer portal "
diff --git a/addons/website_sale_options/static/src/js/website_sale.js b/addons/website_sale_options/static/src/js/website_sale.js
index da7025d65bb..27da88ae866 100644
--- a/addons/website_sale_options/static/src/js/website_sale.js
+++ b/addons/website_sale_options/static/src/js/website_sale.js
@@ -9,7 +9,7 @@ require('website_sale.website_sale');
$('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
.off('click')
.removeClass('a-submit')
- .click(function (event) {
+ .click(_.debounce(function (event) {
var $form = $(this).closest('form');
var quantity = parseFloat($form.find('input[name="add_qty"]').val() || 1);
var product_id = parseInt($form.find('input[type="hidden"][name="product_id"], input[type="radio"][name="product_id"]:checked').first().val(),10);
@@ -96,6 +96,6 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
});
});
return false;
- });
+ }, 200, true));
});
diff --git a/odoo/addons/base/models/ir_property.py b/odoo/addons/base/models/ir_property.py
index 99b63a95887..63ca6d2d6d0 100644
--- a/odoo/addons/base/models/ir_property.py
+++ b/odoo/addons/base/models/ir_property.py
@@ -189,7 +189,7 @@ class Property(models.Model):
if not values:
return
- if not default_value:
+ if default_value is None:
domain = self._get_domain(name, model)
if domain is None:
raise Exception()
diff --git a/odoo/addons/base/models/ir_qweb.py b/odoo/addons/base/models/ir_qweb.py
index 4e3a7eb679a..921f3ce2ee6 100644
--- a/odoo/addons/base/models/ir_qweb.py
+++ b/odoo/addons/base/models/ir_qweb.py
@@ -174,7 +174,7 @@ class IrQWeb(models.AbstractModel, QWeb):
value=ast.Call(
func=ast.Attribute(
value=ast.Name(id='self', ctx=ast.Load()),
- attr='_get_asset',
+ attr='_get_asset_nodes',
ctx=ast.Load()
),
args=[
@@ -286,15 +286,28 @@ class IrQWeb(models.AbstractModel, QWeb):
# method called by computing code
+ def get_asset_bundle(self, xmlid, files, remains=None, env=None):
+ return AssetsBundle(xmlid, files, remains=remains, env=env)
+
+ # compatibility to remove after v11 - DEPRECATED
+ @tools.conditional(
+ 'xml' not in tools.config['dev_mode'],
+ tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async', keys=("website_id",)),
+ )
+ def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
+ files, remains = self._get_asset_content(xmlid, options)
+ asset = self.get_asset_bundle(xmlid, files, remains, env=self.env)
+ return asset.to_html(css=css, js=js, debug=debug, async=async, url_for=(values or {}).get('url_for', lambda url: url))
+
@tools.conditional(
# in non-xml-debug mode we want assets to be cached forever, and the admin can force a cache clear
# by restarting the server after updating the source code (or using the "Clear server cache" in debug tools)
'xml' not in tools.config['dev_mode'],
tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async', keys=("website_id",)),
)
- def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
+ def _get_asset_nodes(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
files, remains = self._get_asset_content(xmlid, options)
- asset = AssetsBundle(xmlid, files, env=self.env)
+ asset = self.get_asset_bundle(xmlid, files, env=self.env)
remains = [node for node in remains if (css and node[0] == 'link') or (js and node[0] != 'link')]
return remains + asset.to_node(css=css, js=js, debug=debug, async=async)
|