diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py
index 0fcb9485d76d4ca5c54ecfde482310ce0aa684cf..2cb66869051ef490e47a823e4062a112ecede569 100644
--- a/addons/website_quote/controllers/main.py
+++ b/addons/website_quote/controllers/main.py
@@ -2,8 +2,10 @@
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
 import werkzeug
+from functools import partial
 
 from odoo import fields, http, _
+from odoo.tools import formatLang
 from odoo.http import request
 from odoo.addons.website_mail.controllers.main import _message_post_helper
 
@@ -104,8 +106,17 @@ class sale_quote(http.Controller):
             _message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': token, 'token_field': 'access_token'} if token else {})
         return werkzeug.utils.redirect("/quote/%s/%s?message=2" % (order_id, token))
 
+    # Deprecated because override opportunities are **really** limited
+    # In fact it should be removed in master ASAP
     @http.route(['/quote/update_line'], type='json', auth="public", website=True)
     def update(self, line_id, remove=False, unlink=False, order_id=None, token=None, **post):
+        values = self.update_line_dict(line_id, remove, unlink, order_id, token, **post)
+        if values:
+            return [values['order_line_product_uom_qty'], values['order_amount_total']]
+        return values
+
+    @http.route(['/quote/update_line_dict'], type='json', auth="public", website=True)
+    def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, token=None, input_quantity=False, **kwargs):
         Order = request.env['sale.order'].sudo().browse(int(order_id))
         if token != Order.access_token:
             return request.render('website.404')
@@ -115,10 +126,27 @@ class sale_quote(http.Controller):
         if unlink:
             OrderLine.unlink()
             return False
-        number = -1 if remove else 1
-        quantity = OrderLine.product_uom_qty + number
+
+        if input_quantity is not False:
+            quantity = input_quantity
+        else:
+            number = -1 if remove else 1
+            quantity = OrderLine.product_uom_qty + number
+
+        if quantity < 0:
+            quantity = 0.0
         OrderLine.write({'product_uom_qty': quantity})
-        return [str(quantity), str(Order.amount_total)]
+        currency = Order.currency_id
+        format_price = partial(formatLang, request.env, digits=currency.decimal_places)
+
+        return {
+            'order_line_product_uom_qty': str(quantity),
+            'order_line_price_total': format_price(OrderLine.price_total),
+            'order_line_price_subtotal': format_price(OrderLine.price_subtotal),
+            'order_amount_total': format_price(Order.amount_total),
+            'order_amount_untaxed': format_price(Order.amount_untaxed),
+            'order_amount_tax': format_price(Order.amount_tax),
+        }
 
     @http.route(["/quote/template/<model('sale.quote.template'):quote>"], type='http', auth="user", website=True)
     def template_view(self, quote, **post):
diff --git a/addons/website_quote/static/src/js/website_quotation.js b/addons/website_quote/static/src/js/website_quotation.js
index 04705686f32563fcec1c1ba5ecf17af7bdd1e2a8..6985625c378ee92f8e7119bed3c24f1ea84be55d 100644
--- a/addons/website_quote/static/src/js/website_quotation.js
+++ b/addons/website_quote/static/src/js/website_quotation.js
@@ -15,28 +15,160 @@ if(!$('.o_website_quote').length) {
         events: {
             'click' : 'onClick',
         },
-        onClick: function(ev){
-            ev.preventDefault();
+        /**
+         * @override
+         */
+        start: function () {
             var self = this;
-            var href = this.$el.attr("href");
-            var order_id = href.match(/order_id=([0-9]+)/);
-            var line_id = href.match(/update_line\/([0-9]+)/);
-            var token = href.match(/token=(.*)/);
-            ajax.jsonRpc("/quote/update_line", 'call', {
-                'line_id': line_id[1],
-                'order_id': parseInt(order_id[1]),
-                'token': token[1],
-                'remove': self.$el.is('[href*="remove"]'),
-                'unlink': self.$el.is('[href*="unlink"]')
-            }).then(function (data) {
-                if(!data){
-                    location.reload();
-                }
-                self.$el.parents('.input-group:first').find('.js_quantity').val(data[0]);
-                $('[data-id="total_amount"]>span').html(data[1]);
+            return this._super.apply(this, arguments).then(function () {
+                self.elems = self._getUpdatableElements();
+                self.elems.$lineQuantity.change(function (ev) {
+                    var quantity = parseInt(this.value);
+                    self._onChangeQuantity(quantity);
+                });
             });
+        },
+        /**
+         * Process the change in line quantity
+         *
+         * @private
+         * @param {Int} quantity, the new quantity of the line
+         *    If not present it will increment/decrement the existing quantity
+         */
+        _onChangeQuantity: function (quantity) {
+            var href = this.$el.attr("href");
+            var order_id = href.match(/order_id=([0-9]+)/)[1];
+            var line_id = href.match(/update_line(_dict)?\/([0-9]+)/)[2];
+            var token = href.match(/token=([\w\d-]*)/)[1];
+
+            var callParams = {
+                'line_id': parseInt(line_id),
+                'order_id': parseInt(order_id),
+                'token': token,
+                'remove': this.$el.is('[href*="remove"]'),
+                'unlink': this.$el.is('[href*="unlink"]'),
+                'input_quantity': quantity >= 0 ? quantity : false,
+            };
+            this._callUpdateLineRoute(callParams).then(this._updateOrderValues.bind(this));
             return false;
         },
+        /**
+         * Reacts to the click on the -/+ buttons
+         *
+         * @param {Event} ev
+         */
+        onClick: function (ev) {
+            ev.preventDefault();
+            return this._onChangeQuantity();
+        },
+        /**
+         * Calls the route to get updated values of the line and order
+         * when the quantity of a product has changed
+         *
+         * @private
+         * @param {Object} params
+         * @return {Deferred}
+         */
+        _callUpdateLineRoute: function (params) {
+            var def = new $.Deferred();
+            ajax.jsonRpc("/quote/update_line_dict", 'call', params)
+                .then(def.resolve.bind(def))
+                .fail(function () {
+                    // Compatibility: the server may not have been restarted
+                    // So the real route may not exist
+                    delete params.input_quantity;
+                    ajax.jsonRpc("/quote/update_line", 'call', params)
+                        .fail(def.reject.bind(def))
+                        .then(function (data) {
+                            // Data is an array, convert it to a dict
+                            var actualData = data;
+                            if (data) {
+                                actualData = {
+                                    order_amount_total: data[1],
+                                    order_line_product_uom_qty: data[0],
+                                };
+                            }
+                            def.resolve(actualData);
+                        });
+                });
+            return def;
+        },
+        /**
+         * Processes data from the server to update the UI
+         *
+         * @private
+         * @param {Object} data: contains order and line updated values
+         */
+        _updateOrderValues: function (data) {
+            if (!data) {
+                window.location.reload();
+            }
+
+            var orderAmountTotal = data.order_amount_total;
+            var orderAmountUntaxed = data.order_amount_untaxed;
+            var orderAmountTax = data.order_amount_tax;
+
+            var lineProductUomQty = data.order_line_product_uom_qty;
+            var linePriceTotal = data.order_line_price_total;
+            var linePriceSubTotal = data.order_line_price_subtotal;
+
+            this.elems.$lineQuantity.val(lineProductUomQty)
+
+            if (this.elems.$linePriceTotal.length && linePriceTotal !== undefined) {
+                this.elems.$linePriceTotal.text(linePriceTotal);
+            }
+            if (this.elems.$linePriceSubTotal.length && linePriceSubTotal !== undefined) {
+                this.elems.$linePriceSubTotal.text(linePriceSubTotal);
+            }
+
+            if (orderAmountUntaxed !== undefined) {
+                this.elems.$orderAmountUntaxed.text(orderAmountUntaxed);
+            }
+
+            if (orderAmountTax !== undefined) {
+                this.elems.$orderAmountTax.text(orderAmountTax);
+            }
+
+            if (orderAmountTotal !== undefined) {
+                this.elems.$orderAmountTotal.text(orderAmountTotal);
+            }
+        },
+        /**
+         * Locate in the DOM the elements to update
+         * Mostly for compatibility, when the module has not been upgraded
+         * In that case, we need to fall back to some other elements
+         *
+         * @private
+         * @return {Object}: Jquery elements to update
+         */
+        _getUpdatableElements: function () {
+            var $parentTr = this.$el.parents('tr:first');
+            var $linePriceTotal = $parentTr.find('.oe_order_line_price_total .oe_currency_value');
+            var $linePriceSubTotal = $parentTr.find('.oe_order_line_price_subtotal .oe_currency_value');
+
+            if (!$linePriceTotal.length && !$linePriceSubTotal.length) {
+                $linePriceTotal = $linePriceSubTotal = $parentTr.find('.oe_currency_value').last();
+            }
+
+            var $orderAmountUntaxed = $('[data-id="total_untaxed"]>span');
+            var $orderAmountTax = $('[data-id="total_taxes"]>span');
+            var $orderAmountTotal = $('[data-id="total_amount"]>span');
+
+            if (!$orderAmountUntaxed.length && !$orderAmountTax.length) {
+                $orderAmountUntaxed = $orderAmountTotal.eq(1);
+                $orderAmountTax = $orderAmountTotal.eq(2);
+                $orderAmountTotal = $orderAmountTotal.eq(0).add($orderAmountTotal.eq(3));
+            }
+
+            return {
+                $lineQuantity: this.$el.parents('.input-group:first').find('.js_quantity'),
+                $linePriceSubTotal: $linePriceSubTotal,
+                $linePriceTotal: $linePriceTotal,
+                $orderAmountUntaxed: $orderAmountUntaxed,
+                $orderAmountTax: $orderAmountTax,
+                $orderAmountTotal: $orderAmountTotal,
+            }
+        }
     });
 
     var update_button_list = [];
diff --git a/addons/website_quote/views/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml
index 2aa63f2f7308fc1db5858feee4b7b4de72e39fe4..1b4c906a5004d31d72fa9261ede95ef78f534459 100644
--- a/addons/website_quote/views/website_quote_templates.xml
+++ b/addons/website_quote/views/website_quote_templates.xml
@@ -69,11 +69,11 @@
                                             t-options="{'widget': 'monetary', 'display_currency': quotation.pricelist_id.currency_id}"/>
                                     </div>
                               </td>
-                              <td class="text-right" groups="sale.group_show_price_subtotal">
+                              <td class="text-right oe_order_line_price_subtotal" groups="sale.group_show_price_subtotal">
                                   <span t-field="line.price_subtotal"
                                         t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
                               </td>
-                              <td class="text-right" groups="sale.group_show_price_total">
+                              <td class="text-right oe_order_line_price_total" groups="sale.group_show_price_total">
                                   <span t-field="line.price_total"
                                         t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
                               </td>
@@ -102,7 +102,7 @@
                             <td></td><td></td><td></td><td></td>
                             <td class="text-right"><strong>Subtotal:</strong></td>
                             <td class="text-right">
-                                <strong data-id="total_amount" t-field="quotation.amount_untaxed" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
+                                <strong data-id="total_untaxed" t-field="quotation.amount_untaxed" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
                             </td>
                             <td></td>
                         </tr>
@@ -111,7 +111,7 @@
                             <td></td><td></td><td></td><td></td>
                             <td class="text-right">Taxes:</td>
                             <td class="text-right">
-                                <span data-id="total_amount" t-field="quotation.amount_tax" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
+                                <span data-id="total_taxes" t-field="quotation.amount_tax" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
                             </td>
                             <td></td>
                         </tr>