diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index da20b325a3ba41225bde01b27148864d53db36dc..2f734dde9baafce4ef7f3fb0b2e3dfe4acde8f50 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -756,7 +756,7 @@ class WizardMultiChartsAccounts(models.TransientModel):
             if company_id:
                 company = self.env['res.company'].browse(company_id)
                 currency_id = company.on_change_country(company.country_id.id)['value']['currency_id']
-                res.update({'currency_id': currency_id})
+                res.update({'currency_id': currency_id.id})
 
         chart_templates = account_chart_template.search([('visible', '=', True)])
         if chart_templates:
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
index e9588516a0ca8431ef59738aefdfd1086106d8d3..3422edd8cb5cba774f1db824ff4cb9fdc5239e6e 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -68,7 +68,7 @@ class ChangeProductionQty(models.TransientModel):
                                  cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency)
                 quantity = wo.qty_production - wo.qty_produced
                 if production.product_id.tracking == 'serial':
-                    quantity = 1.0 if float_is_zero(quantity, precision_digits=precision) else 0.0
+                    quantity = 1.0 if not float_is_zero(quantity, precision_digits=precision) else 0.0
                 else:
                     quantity = quantity if (quantity > 0) else 0
                 if float_is_zero(quantity, precision_digits=precision):
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 8cf2f5d9a7ddf0786264350a482839b10a1e6a01..65e4de7faa1469436f20b04899c6509cc5e391f3 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1125,12 +1125,15 @@ var ClientListScreenWidget = ScreenWidget.extend({
         var self = this;
         var order = this.pos.get_order();
         if( this.has_client_changed() ){
-            if ( this.new_client ) {
+            var default_fiscal_position_id = _.find(this.pos.fiscal_positions, function(fp) {
+                return fp.id === self.pos.config.default_fiscal_position_id[0];
+            });
+            if ( this.new_client && this.new_client.property_account_position_id ) {
                 order.fiscal_position = _.find(this.pos.fiscal_positions, function (fp) {
                     return fp.id === self.new_client.property_account_position_id[0];
-                });
+                }) || default_fiscal_position_id;
             } else {
-                order.fiscal_position = undefined;
+                order.fiscal_position = default_fiscal_position_id;
             }
 
             order.set_client(this.new_client);
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index fc21bb7c4bdffc2c1b09ea28d549e01998237a0c..d7e9f71705d16bf27f9ad7209ce6e3c2832f95de 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -46,10 +46,13 @@ class ProductCategory(models.Model):
                 category.complete_name = category.name
 
     def _compute_product_count(self):
-        read_group_res = self.env['product.template'].read_group([('categ_id', 'in', self.ids)], ['categ_id'], ['categ_id'])
+        read_group_res = self.env['product.template'].read_group([('categ_id', 'child_of', self.ids)], ['categ_id'], ['categ_id'])
         group_data = dict((data['categ_id'][0], data['categ_id_count']) for data in read_group_res)
         for categ in self:
-            categ.product_count = group_data.get(categ.id, 0)
+            product_count = 0
+            for sub_categ_id in categ.search([('id', 'child_of', categ.id)]).ids:
+                product_count += group_data.get(sub_categ_id, 0)
+            categ.product_count = product_count
 
     @api.constrains('parent_id')
     def _check_category_recursion(self):
diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml
index 31eeba88bde6de77c254dcf543e755fa8ee2644c..6393f99d003ebe268d68b30f55f58cfbb0f4987a 100644
--- a/addons/product/views/product_views.xml
+++ b/addons/product/views/product_views.xml
@@ -112,7 +112,7 @@
         <field name="arch" type="xml">
             <search string="Product">
                 <field name="name" string="Product" filter_domain="['|','|',('default_code','ilike',self),('name','ilike',self),('barcode','ilike',self)]"/>
-                <field name="categ_id" filter_domain="[('categ_id', 'child_of', self)]"/>
+                <field name="categ_id" filter_domain="[('categ_id', 'child_of', raw_value)]"/>
                 <separator/>
                 <filter string="Services" name="services" domain="[('type','=','service')]"/>
                 <filter string="Products" name="consumable" domain="[('type', 'in', ['consu', 'product'])]"/>
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 2e108e94e3dd2d294596f84752b052858def0b49..bfeb11ccd7fec384adb294353ca56fbd98e15ff5 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -245,7 +245,8 @@ class Project(models.Model):
         project = super(Project, self).copy(default)
         for follower in self.message_follower_ids:
             project.message_subscribe(partner_ids=follower.partner_id.ids, subtype_ids=follower.subtype_ids.ids)
-        self.map_tasks(project.id)
+        if 'tasks' not in default:
+            self.map_tasks(project.id)
         return project
 
     @api.model
@@ -269,6 +270,8 @@ class Project(models.Model):
         if 'active' in vals:
             # archiving/unarchiving a project does it on its tasks, too
             self.with_context(active_test=False).mapped('tasks').write({'active': vals['active']})
+            # archiving/unarchiving a project implies that we don't want to use the analytic account anymore
+            self.with_context(active_test=False).mapped('analytic_account_id').write({'active': vals['active']})
         if vals.get('partner_id') or vals.get('privacy_visibility'):
             for project in self.filtered(lambda project: project.privacy_visibility == 'portal'):
                 project.message_subscribe(project.partner_id.ids)
diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js
index d567551873f8bb21701b3f2850fde1253c3be324..a2923d1a60765a1c071485fb94e5e5c0db640444 100644
--- a/addons/web/static/src/js/chrome/search_inputs.js
+++ b/addons/web/static/src/js/chrome/search_inputs.js
@@ -153,7 +153,7 @@ var Field = Input.extend( /** @lends instance.web.search.Field# */ {
             value_to_domain = function (facetValue) {
                 return Domain.prototype.stringToArray(
                     domain,
-                    {self: self.value_from(facetValue)}
+                    {self: self.value_from(facetValue), raw_value: facetValue.attributes.value}
                 );
             };
         } else {
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index ce1a305277f6addd03107e2270369455eb9aef3a..069e004c702ea033f54150386e623cf8ee105450 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -1176,7 +1176,7 @@ var FieldBinaryFile = AbstractFieldBinary.extend({
     template: 'FieldBinaryFile',
     events: _.extend({}, AbstractFieldBinary.prototype.events, {
         'click': function (event) {
-            if (this.mode === 'readonly' && this.value) {
+            if (this.mode === 'readonly' && this.value && this.recordData.id) {
                 this.on_save_as(event);
             }
         },
@@ -1193,6 +1193,11 @@ var FieldBinaryFile = AbstractFieldBinary.extend({
         this.do_toggle(!!this.value);
         if (this.value) {
             this.$el.empty().append($("<span/>").addClass('fa fa-download'));
+            if (this.recordData.id) {
+                this.$el.css('cursor', 'pointer');
+            } else {
+                this.$el.css('cursor', 'not-allowed');
+            }
             if (this.filename_value) {
                 this.$el.append(" " + this.filename_value);
             }
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 6a976c3a5884c3de259c2452387908d79f762acb..4e6c3692aad33640697e654af5148eda728a986f 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -81,7 +81,7 @@ return AbstractRenderer.extend({
             setTimeout(function () {
                 self.$el.empty();
                 var chart = self['_render' + _.str.capitalize(self.state.mode) + 'Chart']();
-                if (chart) {
+                if (chart && chart.tooltip.chartContainer) {
                     self.to_remove = chart.update;
                     nv.utils.onWindowResize(chart.update);
                     chart.tooltip.chartContainer(self.el);
diff --git a/addons/web_editor/static/src/js/rte.js b/addons/web_editor/static/src/js/rte.js
index f2fb27536422eb9685af52152d3b4d5fd0cf813b..3f813367ca31d12ca64b9bd0c2a7aee6db0446e8 100644
--- a/addons/web_editor/static/src/js/rte.js
+++ b/addons/web_editor/static/src/js/rte.js
@@ -355,8 +355,8 @@ var RTE = Widget.extend({
                 var $el = $(this);
 
                 $el.find('[class]').filter(function () {
-                    if (!this.className.match(/\S/)) {
-                        this.removeAttribute("class");
+                    if (!this.getAttribute('class').match(/\S/)) {
+                        this.removeAttribute('class');
                     }
                 });
 
diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js
index 1213b986472662484f19ccb6bab90f01803532c6..bed19b0acde569f7a7a573f41249623a731a975a 100644
--- a/addons/website/static/src/js/website.snippets.animation.js
+++ b/addons/website/static/src/js/website.snippets.animation.js
@@ -415,16 +415,35 @@ animation.registry.media_video = animation.Class.extend({
     start: function () {
         // TODO: this code should be refactored to make more sense and be better
         // integrated with Odoo (this refactoring should be done in master).
-        this.$target.find('iframe').remove();
 
-        if (!this.$target.has('.media_iframe_video_size').length) {
-            var editor = '<div class="css_editable_mode_display">&nbsp;</div>';
-            var size = '<div class="media_iframe_video_size">&nbsp;</div>';
-            this.$target.html(editor+size);
+        var def = this._super.apply(this, arguments);
+        if (this.$target.children('iframe').length) {
+            // There already is an <iframe/>, do nothing
+            return def;
         }
-        // rebuilding the iframe, from https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
-        this.$target.html(this.$target.html()+'<iframe sandbox="allow-scripts allow-same-origin" src="'+_.escape(this.$target.data("oe-expression"))+'" frameborder="0" allowfullscreen="allowfullscreen"></iframe>');
-        return this._super.apply(this, arguments);
+
+        // Bug fix / compatibility: empty the <div/> element as all information
+        // to rebuild the iframe should have been saved on the <div/> element
+        this.$target.empty();
+
+        // Add extra content for size / edition
+        this.$target.append(
+            '<div class="css_editable_mode_display">&nbsp;</div>' +
+            '<div class="media_iframe_video_size">&nbsp;</div>'
+        );
+
+        // Rebuild the iframe. Depending on version / compatibility / instance,
+        // the src is saved in the 'data-src' attribute or the
+        // 'data-oe-expression' one (the latter is used as a workaround in 10.0
+        // system but should obviously be reviewed in master).
+        this.$target.append($('<iframe/>', {
+            src: _.escape(this.$target.data('oe-expression') || this.$target.data('src')),
+            frameborder: '0',
+            allowfullscreen: 'allowfullscreen',
+            sandbox: 'allow-scripts allow-same-origin', // https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
+        }));
+
+        return def;
     },
 });
 
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index d3235391fe2d1ad5639eb8b2c9cf11af6be15f5c..19c3f3b5b029e0f9bf5b8172535a36864d2a3032 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -429,7 +429,7 @@ class WebsiteSale(http.Controller):
             Partner = order.partner_id.with_context(show_address=1).sudo()
             shippings = Partner.search([
                 ("id", "child_of", order.partner_id.commercial_partner_id.ids),
-                '|', ("type", "=", "delivery"), ("id", "=", order.partner_id.commercial_partner_id.id)
+                '|', ("type", "in", ["delivery", "other"]), ("id", "=", order.partner_id.commercial_partner_id.id)
             ], order='id desc')
             if shippings:
                 if kw.get('partner_id') or 'use_billing' in kw:
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 339645a46aa4955c9e28ea6bf14fc7c0e3a8936b..332c1bb238c3e3593115f2d278ae0e5f45e148ed 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1106,6 +1106,7 @@
                                             <t t-foreach="shippings" t-as="ship">
                                                 <div class="col-sm-12 col-md-6 one_kanban">
                                                     <t t-call="website_sale.address_kanban">
+                                                        <t t-set="actual_partner" t-value="order.partner_id" />
                                                         <t t-set='contact' t-value="ship"/>
                                                         <t t-set='selected' t-value="order.partner_shipping_id==ship"/>
                                                         <t t-set='readonly' t-value="bool(len(shippings)==1)"/>
@@ -1139,7 +1140,7 @@
                 </t>
                 <input type='submit'/>
             </form>
-            <a class='btn btn-link pull-right fa fa-edit js_edit_address no-decoration' title="Edit this address"></a>
+            <a t-if="not actual_partner or (ship.id in actual_partner.ids + actual_partner.child_ids.ids)" class='btn btn-link pull-right fa fa-edit js_edit_address no-decoration' title="Edit this address"></a>
             <div t-att-class="'panel panel-default %s' % (selected and 'border_primary' or 'js_change_shipping')">
                 <div class='panel-body' style='min-height: 130px;'>
                     <t t-esc="contact" t-options="dict(widget='contact', fields=['name', 'address'], no_marker=True)"/>
@@ -1175,7 +1176,9 @@
                                 <t t-if="mode == ('new', 'billing')">
                                     <h3 class="page-header mt32 ml16">Your Address
                                         <small> or </small>
-                                        <t t-set='connect' t-value="request.env['ir.config_parameter'].sudo().get_param('auth_signup.allow_uninvited') == 'True' and ('signup', 'Sign Up') or ('login', 'Log In')"/>
+                                        <t t-set="signup_text">Sign Up</t>
+                                        <t t-set="login_text">Log In</t>
+                                        <t t-set='connect' t-value="request.env['ir.config_parameter'].sudo().get_param('auth_signup.allow_uninvited') == 'True' and ('signup', signup_text) or ('login', login_text)"/>
                                         <a t-attf-href='/web/{{connect[0]}}?redirect=/shop/checkout' class='btn btn-primary' style="margin-top: -11px"><t t-esc='connect[1]'/></a>
                                     </h3>
                                 </t>
diff --git a/doc/cla/individual/mtantin.md b/doc/cla/individual/mtantin.md
new file mode 100644
index 0000000000000000000000000000000000000000..b3aee232ccf8661720de8655e69f0b279d28fcc6
--- /dev/null
+++ b/doc/cla/individual/mtantin.md
@@ -0,0 +1,11 @@
+France, 2017-11-29
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Maximilien TANTIN <maximilien.tantin@gmail.com> https://github.com/MTantin
\ No newline at end of file
diff --git a/doc/cla/individual/satriani-vai.md b/doc/cla/individual/satriani-vai.md
new file mode 100644
index 0000000000000000000000000000000000000000..df2c38eb7be91042b5bb47badaf66e3860557e60
--- /dev/null
+++ b/doc/cla/individual/satriani-vai.md
@@ -0,0 +1,11 @@
+Germany, 2017-12-01
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Alex Vai satriani-vai@users.noreply.github.com https://github.com/satriani-vai
diff --git a/odoo/addons/base/ir/ir_cron.py b/odoo/addons/base/ir/ir_cron.py
index bcb2df904a56168e7f1b135f9645df237c2c0e6c..5be123d6ef9c9ee029d59e5476a6602842511fad 100644
--- a/odoo/addons/base/ir/ir_cron.py
+++ b/odoo/addons/base/ir/ir_cron.py
@@ -5,7 +5,7 @@ import threading
 import time
 import psycopg2
 import pytz
-from datetime import datetime
+from datetime import datetime, timedelta
 from dateutil.relativedelta import relativedelta
 
 import odoo
@@ -15,6 +15,7 @@ from odoo.exceptions import UserError
 _logger = logging.getLogger(__name__)
 
 BASE_VERSION = odoo.modules.load_information_from_description_file('base')['version']
+MAX_FAIL_TIME = timedelta(hours=5)  # chosen with a fair roll of the dice
 
 
 class BadVersion(Exception):
@@ -169,7 +170,7 @@ class ir_cron(models.Model):
                 (version,) = cr.fetchone()
                 cr.execute("SELECT COUNT(*) FROM ir_module_module WHERE state LIKE %s", ['to %'])
                 (changes,) = cr.fetchone()
-                if not version or changes:
+                if version is None:
                     raise BadModuleState()
                 elif version != BASE_VERSION:
                     raise BadVersion()
@@ -180,6 +181,19 @@ class ir_cron(models.Model):
                               ORDER BY priority""")
                 jobs = cr.dictfetchall()
 
+            if changes:
+                if not jobs:
+                    raise BadModuleState()
+                # nextcall is never updated if the cron is not executed,
+                # it is used as a sentinel value to check whether cron jobs
+                # have been locked for a long time (stuck)
+                parse = fields.Datetime.from_string
+                oldest = min([parse(job['nextcall']) for job in jobs])
+                if datetime.now() - oldest > MAX_FAIL_TIME:
+                    odoo.modules.reset_modules_state(db_name)
+                else:
+                    raise BadModuleState()
+
             for job in jobs:
                 lock_cr = db.cursor()
                 try:
diff --git a/odoo/models.py b/odoo/models.py
index f374eeac97f6b4b1e7d27d06d47c3d8163eb01af..5121c36cf46ae57387a101fec6cdda2d3ff5d309 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -1657,7 +1657,8 @@ class BaseModel(object):
                     order = '"%s" %s' % (order_field, '' if len(order_split) == 1 else order_split[1])
                     orderby_terms.append(order)
             elif order_field in aggregated_fields:
-                orderby_terms.append(order_part)
+                order_split[0] = '"' + order_field + '"'
+                orderby_terms.append(' '.join(order_split))
             else:
                 # Cannot order by a field that will not appear in the results (needs to be grouped or aggregated)
                 _logger.warn('%s: read_group order by `%s` ignored, cannot sort on empty columns (not grouped/aggregated)',
diff --git a/odoo/modules/__init__.py b/odoo/modules/__init__.py
index 530f81ff5e39456836c70580abc5008231eb3f9f..b223df992802d46b3ccf5577929db02ef6f8cd75 100644
--- a/odoo/modules/__init__.py
+++ b/odoo/modules/__init__.py
@@ -7,7 +7,7 @@
 
 from . import db, graph, loading, migration, module, registry
 
-from odoo.modules.loading import load_modules
+from odoo.modules.loading import load_modules, reset_modules_state
 
 from odoo.modules.module import (
     adapt_version,
diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py
index 5952fa499b9993e3e28524f66f8a240895d63b95..1634e57f71153ada01975fced917ed01cc9ce9d4 100644
--- a/odoo/modules/loading.py
+++ b/odoo/modules/loading.py
@@ -423,3 +423,24 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
 
     finally:
         cr.close()
+
+
+def reset_modules_state(db_name):
+    """
+    Resets modules flagged as "to x" to their original state
+    """
+    # Warning, this function was introduced in response to commit 763d714
+    # which locks cron jobs for dbs which have modules marked as 'to %'.
+    # The goal of this function is to be called ONLY when module
+    # installation/upgrade/uninstallation fails, which is the only known case
+    # for which modules can stay marked as 'to %' for an indefinite amount
+    # of time
+    db = odoo.sql_db.db_connect(db_name)
+    with db.cursor() as cr:
+        cr.execute(
+            "UPDATE ir_module_module SET state='installed' WHERE state IN ('to remove', 'to upgrade')"
+        )
+        cr.execute(
+            "UPDATE ir_module_module SET state='uninstalled' WHERE state='to install'"
+        )
+        _logger.warning("Transient module states were reset")
diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py
index e0cfb532fd136319908e78ef39504ec167461950..3db3dc4e2b720d7bc69ce181e55c97d92e5f23cb 100644
--- a/odoo/modules/registry.py
+++ b/odoo/modules/registry.py
@@ -80,7 +80,11 @@ class Registry(Mapping):
                 try:
                     registry.setup_signaling()
                     # This should be a method on Registry
-                    odoo.modules.load_modules(registry._db, force_demo, status, update_module)
+                    try:
+                        odoo.modules.load_modules(registry._db, force_demo, status, update_module)
+                    except Exception:
+                        odoo.modules.reset_modules_state(db_name)
+                        raise
                 except Exception:
                     _logger.exception('Failed to load registry')
                     del cls.registries[db_name]