[MERGE] forward port of branch 7.0 up to e5533d0
authorChristophe Simonis <chs@odoo.com>
Thu, 19 Jun 2014 13:32:32 +0000 (15:32 +0200)
committerChristophe Simonis <chs@odoo.com>
Thu, 19 Jun 2014 13:32:32 +0000 (15:32 +0200)
1  2 
addons/mrp/mrp.py
addons/web/static/src/js/formats.js
addons/web/static/src/js/search.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/view_list_editable.js

diff --combined addons/mrp/mrp.py
@@@ -27,7 -27,8 +27,7 @@@ from openerp.osv import fields, osv, or
  from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
  from openerp.tools import float_compare
  from openerp.tools.translate import _
 -from openerp import netsvc
 -from openerp import tools
 +from openerp import tools, SUPERUSER_ID
  from openerp import SUPERUSER_ID
  from openerp.addons.product import _common
  
@@@ -72,6 -73,7 +72,6 @@@ class mrp_workcenter(osv.osv)
              value = {'costs_hour': cost.standard_price}
          return {'value': value}
  
 -mrp_workcenter()
  
  
  class mrp_routing(osv.osv):
          'active': lambda *a: 1,
          'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.routing', context=context)
      }
 -mrp_routing()
  
  class mrp_routing_workcenter(osv.osv):
      """
          'cycle_nbr': lambda *a: 1.0,
          'hour_nbr': lambda *a: 0.0,
      }
 -mrp_routing_workcenter()
  
  class mrp_bom(osv.osv):
      """
@@@ -426,18 -430,6 +426,18 @@@ class mrp_production(osv.osv)
              location_id = False
          return location_id
  
 +    def _get_progress(self, cr, uid, ids, name, arg, context=None):
 +        """ Return product quantity percentage """
 +        result = dict.fromkeys(ids, 100)
 +        for mrp_production in self.browse(cr, uid, ids, context=context):
 +            if mrp_production.product_qty:
 +                done = 0.0
 +                for move in mrp_production.move_created_ids2:
 +                    if not move.scrapped and move.product_id == mrp_production.product_id:
 +                        done += move.product_qty
 +                result[mrp_production.id] = done / mrp_production.product_qty * 100
 +        return result
 +
      _columns = {
          'name': fields.char('Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
          'origin': fields.char('Source Document', size=64, readonly=True, states={'draft': [('readonly', False)]},
          'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}),
          'product_uos_qty': fields.float('Product UoS Quantity', readonly=True, states={'draft': [('readonly', False)]}),
          'product_uos': fields.many2one('product.uom', 'Product UoS', readonly=True, states={'draft': [('readonly', False)]}),
 +        'progress': fields.function(_get_progress, type='float',
 +            string='Production progress'),
  
          'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
              readonly=True, states={'draft':[('readonly',False)]},
          stock_mov_obj = self.pool.get('stock.move')
          production = self.browse(cr, uid, production_id, context=context)
  
 -        wf_service = netsvc.LocalService("workflow")
          if not production.move_lines and production.state == 'ready':
              # trigger workflow if not products to consume (eg: services)
 -            wf_service.trg_validate(uid, 'mrp.production', production_id, 'button_produce', cr)
 +            self.signal_button_produce(cr, uid, [production_id])
  
          produced_qty = 0
          for produced_product in production.move_created_ids2:
                      new_parent_ids.append(final_product.id)
              for new_parent_id in new_parent_ids:
                  stock_mov_obj.write(cr, uid, [raw_product.id], {'move_history_ids': [(4,new_parent_id)]})
 -
 -        wf_service.trg_validate(uid, 'mrp.production', production_id, 'button_produce_done', cr)
 +        self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context)
 +        self.signal_button_produce_done(cr, uid, [production_id])
          return True
  
      def _costs_generate(self, cr, uid, production):
                  account = wc.costs_hour_account_id.id
                  if value and account:
                      amount += value
 -                    analytic_line_obj.create(cr, uid, {
 +                    # we user SUPERUSER_ID as we do not garantee an mrp user
 +                    # has access to account analytic lines but still should be
 +                    # able to produce orders
 +                    analytic_line_obj.create(cr, SUPERUSER_ID, {
                          'name': wc_line.name + ' (H)',
                          'amount': value,
                          'account_id': account,
                  account = wc.costs_cycle_account_id.id
                  if value and account:
                      amount += value
 -                    analytic_line_obj.create(cr, uid, {
 +                    analytic_line_obj.create(cr, SUPERUSER_ID, {
                          'name': wc_line.name+' (C)',
                          'amount': value,
                          'account_id': account,
          return True
  
      def _make_production_line_procurement(self, cr, uid, production_line, shipment_move_id, context=None):
 -        wf_service = netsvc.LocalService("workflow")
          procurement_order = self.pool.get('procurement.order')
          production = production_line.production_id
          location_id = production.location_src_id.id
                      'move_id': shipment_move_id,
                      'company_id': production.company_id.id,
                  })
 -        self._hook_create_post_procurement(cr, uid, production, procurement_id, context=context)
 -        wf_service.trg_validate(uid, procurement_order._name, procurement_id, 'button_confirm', cr)
 +        procurement_order.signal_button_confirm(cr, uid, [procurement_id])
          return procurement_id
  
      def _make_production_internal_shipment_line(self, cr, uid, production_line, shipment_id, parent_move_id, destination_location_id=False, context=None):
          @return: Newly generated Shipment Id.
          """
          shipment_id = False
 -        wf_service = netsvc.LocalService("workflow")
          uncompute_ids = filter(lambda x:x, [not x.product_lines and x.id or False for x in self.browse(cr, uid, ids, context=context)])
          self.action_compute(cr, uid, uncompute_ids, context=context)
          for production in self.browse(cr, uid, ids, context=context):
  
              # Take routing location as a Source Location.
              source_location_id = production.location_src_id.id
-             if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
-                 source_location_id = production.bom_id.routing_id.location_id.id
+             if production.routing_id and production.routing_id.location_id:
+                 source_location_id = production.routing_id.location_id.id
  
              for line in production.product_lines:
                  consume_move_id = self._make_production_consume_line(cr, uid, line, produce_move_id, source_location_id=source_location_id, context=context)
                      self._make_production_line_procurement(cr, uid, line, shipment_move_id, context=context)
  
              if shipment_id:
 -                wf_service.trg_validate(uid, 'stock.picking', shipment_id, 'button_confirm', cr)
 +                self.pool.get('stock.picking').signal_button_confirm(cr, uid, [shipment_id])
              production.write({'state':'confirmed'}, context=context)
          return shipment_id
  
@@@ -1,9 -1,5 +1,9 @@@
  
 -openerp.web.formats = function(instance) {
 +(function() {
 +
 +var instance = openerp;
 +openerp.web.formats = {};
 +
  var _t = instance.web._t;
  
  /**
@@@ -145,7 -141,6 +145,7 @@@ instance.web.format_value = function (v
                  return '';
              }
              console.warn('Field', descriptor, 'had an empty string as value, treating as false...');
 +            return value_if_empty === undefined ?  '' : value_if_empty;
          case false:
          case undefined:
          case Infinity:
          case 'selection': case 'statusbar':
              // Each choice is [value, label]
              if(_.isArray(value)) {
 -                 value = value[0]
 +                 return value[1];
              }
              var result = _(descriptor.selection).detect(function (choice) {
                  return choice[0] === value;
@@@ -225,9 -220,9 +225,9 @@@ instance.web.parse_value = function (va
          case "":
              return value_if_empty === undefined ?  false : value_if_empty;
      }
 +    var tmp;
      switch (descriptor.widget || descriptor.type || (descriptor.field && descriptor.field.type)) {
          case 'integer':
 -            var tmp;
              do {
                  tmp = value;
                  value = value.replace(instance.web._t.database.parameters.thousands_sep, "");
                  throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value));
              return tmp;
          case 'float':
 -            var tmp = Number(value);
 +            tmp = Number(value);
              if (!isNaN(tmp))
                  return tmp;
  
                      value, (date_pattern + ' ' + time_pattern));
              if (datetime !== null)
                  return instance.web.datetime_to_str(datetime);
+             datetime = Date.parseExact(value.replace(/\d+/g, function(m){
+                 return m.length === 1 ? "0" + m : m ;
+             }), (date_pattern + ' ' + time_pattern));
+             if (datetime !== null)
+                 return instance.web.datetime_to_str(datetime);
              datetime = Date.parse(value);
              if (datetime !== null)
                  return instance.web.datetime_to_str(datetime);
              var date = Date.parseExact(value, date_pattern);
              if (date !== null)
                  return instance.web.date_to_str(date);
+             date = Date.parseExact(value.replace(/\d+/g, function(m){
+                 return m.length === 1 ? "0" + m : m ;
+             }), date_pattern);
+             if (date !== null)
+                 return instance.web.date_to_str(date);
              date = Date.parse(value);
              if (date !== null)
                  return instance.web.date_to_str(date);
@@@ -351,4 -356,4 +361,4 @@@ instance.web.round_decimals = function(
      return instance.web.round_precision(value, Math.pow(10,-decimals));
  };
  
 -};
 +})();
@@@ -1,9 -1,4 +1,9 @@@
 -openerp.web.search = function(instance) {
 +
 +(function() {
 +
 +var instance = openerp;
 +openerp.web.search = {};
 +
  var QWeb = instance.web.qweb,
        _t =  instance.web._t,
       _lt = instance.web._lt;
@@@ -70,11 -65,8 +70,11 @@@ my.SearchQuery = B.Collection.extend(
          }, this);
      },
      add: function (values, options) {
 -        options || (options = {});
 -        if (!(values instanceof Array)) {
 +        options = options || {};
 +
 +        if (!values) {
 +            values = [];
 +        } else if (!(values instanceof Array)) {
              values = [values];
          }
  
                      && facet.get('field') === model.get('field');
              });
              if (previous) {
 -                previous.values.add(model.get('values'));
 +                previous.values.add(model.get('values'), _.omit(options, 'at', 'merge'));
                  return;
              }
              B.Collection.prototype.add.call(this, model, options);
          }, this);
 +        // warning: in backbone 1.0+ add is supposed to return the added models,
 +        // but here toggle may delegate to add and return its value directly.
 +        // return value of neither seems actually used but should be tested
 +        // before change, probably
          return this;
      },
      toggle: function (value, options) {
 -        options || (options = {});
 +        options = options || {};
  
          var facet = this.detect(function (facet) {
              return facet.get('category') === value.category
@@@ -160,7 -148,7 +160,7 @@@ my.InputView = instance.web.Widget.exte
              range.setStart(root, 0);
          }
          if (range.endContainer === this.el && range.endOffset === 1) {
 -            range.setEnd(root, root.length)
 +            range.setEnd(root, root.length);
          }
          assert(range.startContainer === root,
                 "selection should be in the input view");
          return {
              start: range.startOffset,
              end: range.endOffset
 -        }
 +        };
      },
      onKeydown: function (e) {
          this.el.normalize();
          setTimeout(function () {
              // Read text content (ignore pasted HTML)
              var data = this.$el.text();
 +            if (!data)
 +                return; 
              // paste raw text back in
              this.$el.empty().text(data);
              this.el.normalize();
@@@ -345,11 -331,11 +345,11 @@@ instance.web.SearchView = instance.web.
          'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
              switch(e.which) {
              case $.ui.keyCode.LEFT:
-                 this.focusPreceding(this);
+                 this.focusPreceding(e.target);
                  e.preventDefault();
                  break;
              case $.ui.keyCode.RIGHT:
-                 this.focusFollowing(this);
+                 this.focusFollowing(e.target);
                  e.preventDefault();
                  break;
              }
       * @param {Boolean} [options.disable_custom_filters=false] do not load custom filters from ir.filters
       */
      init: function(parent, dataset, view_id, defaults, options) {
 -        // Backward compatibility - Can be removed when forward porting
 -        if (Object(options) !== options) {
 -            options = {
 -                hidden: !!options
 -            };
 -        }
 -        // End of Backward compatibility
          this.options = _.defaults(options || {}, {
              hidden: false,
              disable_custom_filters: false,
              });
  
              this.alive($.when(load_view)).then(function (r) {
 -                return self.search_view_loaded(r)
 +                return self.search_view_loaded(r);
              }).fail(function () {
                  self.ready.reject.apply(null, arguments);
              });
  
                  if (item.facet !== undefined) {
                      // regular completion item
 +                    if (item.first) {
 +                        $item.css('borderTop', '1px solid #cccccc');
 +                    }
                      return $item.append(
                          (item.label)
                              ? $('<a>').html(item.label)
              .filter(function (input) { return input.visible(); })
              .invoke('complete', req.term)
              .value()).then(function () {
 -                resp(_(_(arguments).compact()).flatten(true));
 -        });
 +                resp(_(arguments).chain()
 +                    .compact()
 +                    .map(function (completion) {
 +                        if (completion.length && completion[0].facet !== undefined) {
 +                            completion[0].first = true;
 +                        }
 +                        return completion;
 +                    })
 +                    .flatten(true)
 +                    .value());
 +                });
      },
  
      /**
       * @returns instance.web.search.Field
       */
      make_field: function (item, field, parent) {
 +        // M2O combined with selection widget is pointless and broken in search views,
 +        // but has been used in the past for unsupported hacks -> ignore it
 +        if (field.type === "many2one" && item.attrs.widget === "selection"){
 +            item.attrs.widget = undefined;
 +        }
          var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
          if(obj) {
              return new (obj) (item, field, parent || this);
@@@ -982,7 -958,7 +982,7 @@@ instance.web.search.Input = instance.we
       * @returns {jQuery.Deferred<null|Array>}
       */
      complete: function (value) {
 -        return $.when(null)
 +        return $.when(null);
      },
      /**
       * Returns a Facet instance for the provided defaults if they apply to
@@@ -1105,7 -1081,7 +1105,7 @@@ instance.web.search.FilterGroup = insta
              icon: this.icon,
              values: values,
              field: this
 -        }
 +        };
      },
      make_value: function (filter) {
          return {
  
          if (!contexts.length) { return; }
          if (contexts.length === 1) { return contexts[0]; }
 -        return _.extend(new instance.web.CompoundContext, {
 +        return _.extend(new instance.web.CompoundContext(), {
              __contexts: contexts
          });
      },
                  label: _.str.sprintf(self.completion_label.toString(),
                                       _.escape(facet_value.label)),
                  facet: self.make_facet([facet_value])
 -            }
 +            };
          }));
      }
  });
@@@ -1230,7 -1206,7 +1230,7 @@@ instance.web.search.GroupbyGroup = inst
                  get_context: this.proxy('get_context'),
                  get_domain: this.proxy('get_domain'),
                  get_groupby: this.proxy('get_groupby')
 -            }
 +            };
          }
      },
      match_facet: function (facet) {
@@@ -1308,7 -1284,7 +1308,7 @@@ instance.web.search.Field = instance.we
  
          if (contexts.length === 1) { return contexts[0]; }
  
 -        return _.extend(new instance.web.CompoundContext, {
 +        return _.extend(new instance.web.CompoundContext(), {
              __contexts: contexts
          });
      },
              domains.unshift(['|']);
          }
  
 -        return _.extend(new instance.web.CompoundDomain, {
 +        return _.extend(new instance.web.CompoundDomain(), {
              __domains: domains
          });
      }
@@@ -1596,7 -1572,7 +1596,7 @@@ instance.web.search.ManyToOneField = in
          return this.model.call('name_get', [value]).then(function (names) {
              if (_(names).isEmpty()) { return null; }
              return facet_from(self, names[0]);
 -        })
 +        });
      },
      value_from: function (facetValue) {
          return facetValue.get('label');
@@@ -1962,7 -1938,7 +1962,7 @@@ instance.web.search.ExtendedSearchPropo
      },
      changed: function() {
          var nval = this.$(".searchview_extended_prop_field").val();
 -        if(this.attrs.selected == null || nval != this.attrs.selected.name) {
 +        if(this.attrs.selected === null || this.attrs.selected === undefined || nval != this.attrs.selected.name) {
              this.select_field(_.detect(this.fields, function(x) {return x.name == nval;}));
          }
      },
       */
      select_field: function(field) {
          var self = this;
 -        if(this.attrs.selected != null) {
 +        if(this.attrs.selected !== null && this.attrs.selected !== undefined) {
              this.value.destroy();
              this.value = null;
              this.$('.searchview_extended_prop_op').html('');
          }
          this.attrs.selected = field;
 -        if(field == null) {
 +        if(field === null || field === undefined) {
              return;
          }
  
  
      },
      get_proposition: function() {
 -        if ( this.attrs.selected == null)
 +        if (this.attrs.selected === null || this.attrs.selected === undefined)
              return null;
          var field = this.attrs.selected;
          var op_select = this.$('.searchview_extended_prop_op')[0];
@@@ -2140,7 -2116,7 +2140,7 @@@ instance.web.search.ExtendedSearchPropo
      get_value: function() {
          try {
              var val =this.$el.val();
 -            return instance.web.parse_value(val == "" ? 0 : val, {'widget': 'integer'});
 +            return instance.web.parse_value(val === "" ? 0 : val, {'widget': 'integer'});
          } catch (e) {
              return "";
          }
@@@ -2167,7 -2143,7 +2167,7 @@@ instance.web.search.ExtendedSearchPropo
      get_value: function() {
          try {
              var val =this.$el.val();
 -            return instance.web.parse_value(val == "" ? 0.0 : val, {'widget': 'float'});
 +            return instance.web.parse_value(val === "" ? 0.0 : val, {'widget': 'float'});
          } catch (e) {
              return "";
          }
@@@ -2222,6 -2198,6 +2222,6 @@@ instance.web.search.custom_filters = ne
      'id': 'instance.web.search.ExtendedSearchProposition.Id'
  });
  
 -};
 +})();
  
  // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
@@@ -1,6 -1,4 +1,6 @@@
 -openerp.web.form = function (instance) {
 +(function() {
 +
 +var instance = openerp;
  var _t = instance.web._t,
     _lt = instance.web._lt;
  var QWeb = instance.web.qweb;
@@@ -206,7 -204,7 +206,7 @@@ instance.web.FormView = instance.web.Vi
          this.has_been_loaded.resolve();
  
          // Add bounce effect on button 'Edit' when click on readonly page view.
 -        this.$el.find(".oe_form_group_row,.oe_form_field,label").on('click', function (e) {
 +        this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) {
              if(self.get("actual_mode") == "view") {
                  var $button = self.options.$buttons.find(".oe_form_button_edit");
                  $button.openerpBounce();
              self.on_form_changed();
              self.rendering_engine.init_fields();
              self.is_initialized.resolve();
 -            self.do_update_pager(record.id == null);
 +            self.do_update_pager(record.id === null || record.id === undefined);
              if (self.sidebar) {
                 self.sidebar.do_attachement_update(self.dataset, self.datarecord.id);
              }
                      this.dataset.index = this.dataset.ids.length - 1;
                      break;
              }
 -            this.reload();
 +            var def = this.reload();
              this.trigger('pager_action_executed');
 +            return def;
          }
 +        return $.when();
      },
      init_pager: function() {
          var self = this;
              this.$el.find('.oe_form_pager').replaceWith(this.$pager);
          }
          this.$pager.on('click','a[data-pager-action]',function() {
 -            var action = $(this).data('pager-action');
 -            self.execute_pager_action(action);
 +            var $el = $(this);
 +            if ($el.attr("disabled"))
 +                return;
 +            var action = $el.data('pager-action');
 +            var def = $.when(self.execute_pager_action(action));
 +            $el.attr("disabled");
 +            def.always(function() {
 +                $el.removeAttr("disabled");
 +            });
          });
          this.do_update_pager();
      },
  
          var method = call[1];
          if (!_.str.trim(call[2])) {
 -            return {method: method, args: []}
 +            return {method: method, args: []};
          }
  
          var argument_replacement = {
              // form field
              if (self.fields[field]) {
                  var value_ = self.fields[field].get_value();
 -                return value_ == null ? false : value_;
 +                return value_ === null || value_ === undefined ? false : value_;
              }
              // parent field
              var splitted = field.split('.');
                  }
                  var p_val = parent_fields[_.str.trim(splitted[1])];
                  if (p_val !== undefined) {
 -                    return p_val == null ? false : p_val;
 +                    return p_val === null || p_val === undefined ? false : p_val;
                  }
              }
              // string literal
      },
      on_processed_onchange: function(result, processed) {
          try {
 +        var fields = this.fields;
 +        _(result.domain).each(function (domain, fieldname) {
 +            var field = fields[fieldname];
 +            if (!field) { return; }
 +            field.node.attrs.domain = domain;
 +        });
 +            
          if (result.value) {
              this._internal_set_values(result.value, processed);
          }
              });
          }
  
 -        var fields = this.fields;
 -        _(result.domain).each(function (domain, fieldname) {
 -            var field = fields[fieldname];
 -            if (!field) { return; }
 -            field.node.attrs.domain = domain;
 -        });
 -
          return $.Deferred().resolve();
          } catch(e) {
              console.error(e);
                      self.save_list.pop();
                      return $.when();
                  });
 -            };
 +            }
              return iterate();
          });
      },
              } else {
                  $.async_when().done(function () {
                      def.reject();
 -                })
 +                });
              }
          });
          return def.promise();
      reload: function() {
          var self = this;
          return this.reload_mutex.exec(function() {
 -            if (self.dataset.index == null) {
 +            if (self.dataset.index === null || self.dataset.index === undefined) {
                  self.trigger("previous_view");
                  return $.Deferred().reject().promise();
              }
 -            if (self.dataset.index == null || self.dataset.index < 0) {
 +            if (self.dataset.index < 0) {
                  return $.when(self.on_button_new());
              } else {
                  var fields = _.keys(self.fields_view.fields);
                          context: {
                              'bin_size': true,
                              'future_display_name': true
 -                        }
 +                        },
 +                        check_access_rule: true
                      }).then(function(r) {
                          self.trigger('load_record', r);
 +                    }).fail(function (){
 +                        self.do_action('history_back');
                      });
              }
          });
      open_defaults_dialog: function () {
          var self = this;
          var display = function (field, value) {
 +            if (!value) { return value; }
              if (field instanceof instance.web.form.FieldSelection) {
 -                return _(field.values).find(function (option) {
 +                return _(field.get('values')).find(function (option) {
                      return option[0] === value;
                  })[1];
              } else if (field instanceof instance.web.form.FieldMany2One) {
                  return field.get_displayed();
              }
              return value;
 -        }
 +        };
          var fields = _.chain(this.fields)
              .map(function (field) {
                  var value = field.get_value();
                      string: field.string,
                      value: value,
                      displayed: display(field, value),
 -                }
 +                };
              })
              .compact()
              .sortBy(function (field) { return field.string; })
                      string: field.string,
                      value: value,
                      displayed: display(field, value),
 -                }
 +                };
              })
              .value();
  
@@@ -1449,7 -1434,7 +1449,7 @@@ instance.web.form.FormRenderingEngine 
                  row_cols = cols;
              } else if (tagName==='group') {
                  // When <group> <group/><group/> </group>, we need a spacing between the two groups
 -                $td.addClass('oe_group_right')
 +                $td.addClass('oe_group_right');
              }
              row_cols -= colspan;
  
@@@ -1734,7 -1719,7 +1734,7 @@@ instance.web.form.compute_domain = func
  };
  
  instance.web.form.is_bin_size = function(v) {
 -    return /^\d+(\.\d*)? \w+$/.test(v);
 +    return (/^\d+(\.\d*)? \w+$/).test(v);
  };
  
  /**
@@@ -1872,8 -1857,7 +1872,8 @@@ instance.web.form.FormWidget = instance
                      return QWeb.render(template, {
                          debug: instance.session.debug,
                          widget: widget
 -                })},
 +                    });
 +                },
                  gravity: $.fn.tipsy.autoBounds(50, 'nw'),
                  html: true,
                  opacity: 0.85,
@@@ -1988,10 -1972,8 +1988,10 @@@ instance.web.form.WidgetButton = instan
  
          return this.view.do_execute_action(
              _.extend({}, this.node.attrs, {context: context}),
 -            this.view.dataset, this.view.datarecord.id, function () {
 -                self.view.recursive_reload();
 +            this.view.dataset, this.view.datarecord.id, function (reason) {
 +                if (!_.isObject(reason)) {
 +                    self.view.recursive_reload();
 +                }
              });
      },
      check_disable: function() {
@@@ -2096,7 -2078,7 +2096,7 @@@ instance.web.form.AbstractField = insta
       * @param node
       */
      init: function(field_manager, node) {
 -        var self = this
 +        var self = this;
          this._super(field_manager, node);
          this.name = this.node.attrs.name;
          this.field = this.field_manager.get_field_desc(this.name);
@@@ -2264,7 -2246,7 +2264,7 @@@ instance.web.form.ReinitializeFieldMixi
  /**
      Some hack to make placeholders work in ie9.
  */
 -if ($.browser.msie && $.browser.version === "9.0") {
 +if (!('placeholder' in document.createElement('input'))) {    
      document.addEventListener("DOMNodeInserted",function(event){
          var nodename =  event.target.nodeName.toLowerCase();
          if ( nodename === "input" || nodename == "textarea" ) {
@@@ -2440,6 -2422,7 +2440,7 @@@ instance.web.DateTimeWidget = instance.
      type_of_date: "datetime",
      events: {
          'change .oe_datepicker_master': 'change_datetime',
+         'keypress .oe_datepicker_master': 'change_datetime',
      },
      init: function(parent) {
          this._super(parent);
      format_client: function(v) {
          return instance.web.format_value(v, {"widget": this.type_of_date});
      },
-     change_datetime: function() {
-         if (this.is_valid_()) {
+     change_datetime: function(e) {
+         if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
              this.set_value_from_ui_();
              this.trigger("datetime_changed");
          }
@@@ -2670,7 -2653,7 +2671,7 @@@ instance.web.form.FieldText = instance.
          if (! this.get("effective_readonly")) {
              var show_value = instance.web.format_value(this.get('value'), this, '');
              if (show_value === '') {
 -                this.$textarea.css('height', parseInt(this.default_height)+"px");
 +                this.$textarea.css('height', parseInt(this.default_height, 10)+"px");
              }
              this.$textarea.val(show_value);
              if (! this.auto_sized) {
@@@ -2779,7 -2762,6 +2780,7 @@@ instance.web.form.FieldBoolean = instan
          }, this));
          var check_readonly = function() {
              self.$checkbox.prop('disabled', self.get("effective_readonly"));
 +            self.click_disabled_boolean();
          };
          this.on("change:effective_readonly", this, check_readonly);
          check_readonly.call(this);
      focus: function() {
          var input = this.$checkbox && this.$checkbox[0];
          return input ? input.focus() : false;
 +    },
 +    click_disabled_boolean: function(){
 +        var $disabled = this.$el.find('input[type=checkbox]:disabled');
 +        $disabled.each(function (){
 +            $(this).next('div').remove();
 +            $(this).closest("span").append($('<div class="boolean"></div>'));
 +        });
      }
  });
  
@@@ -2825,37 -2800,10 +2826,37 @@@ instance.web.form.FieldSelection = inst
      init: function(field_manager, node) {
          var self = this;
          this._super(field_manager, node);
 -        this.values = _(this.field.selection).chain()
 -            .reject(function (v) { return v[0] === false && v[1] === ''; })
 -            .unshift([false, ''])
 -            .value();
 +        this.set("value", false);
 +        this.set("values", []);
 +        this.records_orderer = new instance.web.DropMisordered();
 +        this.field_manager.on("view_content_has_changed", this, function() {
 +            var domain = new openerp.web.CompoundDomain(this.build_domain()).eval();
 +            if (! _.isEqual(domain, this.get("domain"))) {
 +                this.set("domain", domain);
 +            }
 +        });
 +    },
 +    initialize_field: function() {
 +        instance.web.form.ReinitializeFieldMixin.initialize_field.call(this);
 +        this.on("change:domain", this, this.query_values);
 +        this.set("domain", new openerp.web.CompoundDomain(this.build_domain()).eval());
 +        this.on("change:values", this, this.render_value);
 +    },
 +    query_values: function() {
 +        var self = this;
 +        var def;
 +        if (this.field.type === "many2one") {
 +            var model = new openerp.Model(openerp.session, this.field.relation);
 +            def = model.call("name_search", ['', this.get("domain")], {"context": this.build_context()});
 +        } else {
 +            var values = _.reject(this.field.selection, function (v) { return v[0] === false && v[1] === ''; });
 +            def = $.when(values);
 +        }
 +        this.records_orderer.add(def).then(function(values) {
 +            if (! _.isEqual(values, self.get("values"))) {
 +                self.set("values", values);
 +            }
 +        });
      },
      initialize_content: function() {
          // Flag indicating whether we're in an event chain containing a change
      },
      store_dom_value: function () {
          if (!this.get('effective_readonly') && this.$('select').length) {
 -            this.internal_set_value(
 -                this.values[this.$('select')[0].selectedIndex][0]);
 +            var val = JSON.parse(this.$('select').val());
 +            this.internal_set_value(val);
          }
      },
      set_value: function(value_) {
          this._super(value_);
      },
      render_value: function() {
 -        if (!this.get("effective_readonly")) {
 -            var index = 0;
 -            for (var i = 0, ii = this.values.length; i < ii; i++) {
 -                if (this.values[i][0] === this.get('value')) index = i;
 -            }
 -            this.$el.find('select')[0].selectedIndex = index;
 +        var values = this.get("values");
 +        values =  [[false, this.node.attrs.placeholder || '']].concat(values);
 +        var found = _.find(values, function(el) { return el[0] === this.get("value"); }, this);
 +        if (! found) {
 +            found = [this.get("value"), _t('Unknown')];
 +            values = [found].concat(values);
 +        }
 +        if (! this.get("effective_readonly")) {
 +            this.$().html(QWeb.render("FieldSelectionSelect", {widget: this, values: values}));
 +            this.$("select").val(JSON.stringify(found[0]));
          } else {
 -            var self = this;
 -            var option = _(this.values)
 -                .detect(function (record) { return record[0] === self.get('value'); });
 -            this.$el.text(option ? option[1] : this.values[0][1]);
 +            this.$el.text(found[1]);
          }
      },
      focus: function() {
      }
  });
  
 +instance.web.form.FieldRadio = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
 +    template: 'FieldRadio',
 +    events: {
 +        'click input': 'click_change_value'
 +    },
 +    init: function(field_manager, node) {
 +        /* Radio button widget: Attributes options:
 +        * - "horizontal" to display in column
 +        * - "no_radiolabel" don't display text values
 +        */
 +        this._super(field_manager, node);
 +        this.selection = _.clone(this.field.selection) || [];
 +        this.domain = false;
 +    },
 +    initialize_content: function () {
 +        this.uniqueId = _.uniqueId("radio");
 +        this.on("change:effective_readonly", this, this.render_value);
 +        this.field_manager.on("view_content_has_changed", this, this.get_selection);
 +        this.get_selection();
 +    },
 +    click_change_value: function (event) {
 +        var val = $(event.target).val();
 +        val = this.field.type == "selection" ? val : +val;
 +        if (val == this.get_value()) {
 +            this.set_value(false);
 +        } else {
 +            this.set_value(val);
 +        }
 +    },
 +    /** Get the selection and render it
 +     *  selection: [[identifier, value_to_display], ...]
 +     *  For selection fields: this is directly given by this.field.selection
 +     *  For many2one fields:  perform a search on the relation of the many2one field
 +     */
 +    get_selection: function() {
 +        var self = this;
 +        var selection = [];
 +        var def = $.Deferred();
 +        if (self.field.type == "many2one") {
 +            var domain = instance.web.pyeval.eval('domain', this.build_domain()) || [];
 +            if (! _.isEqual(self.domain, domain)) {
 +                self.domain = domain;
 +                var ds = new instance.web.DataSetStatic(self, self.field.relation, self.build_context());
 +                ds.call('search', [self.domain])
 +                    .then(function (records) {
 +                        ds.name_get(records).then(function (records) {
 +                            selection = records;
 +                            def.resolve();
 +                        });
 +                    });
 +            } else {
 +                selection = self.selection;
 +                def.resolve();
 +            }
 +        }
 +        else if (self.field.type == "selection") {
 +            selection = self.field.selection || [];
 +            def.resolve();
 +        }
 +        return def.then(function () {
 +            if (! _.isEqual(selection, self.selection)) {
 +                self.selection = _.clone(selection);
 +                self.renderElement();
 +                self.render_value();
 +            }
 +        });
 +    },
 +    set_value: function (value_) {
 +        if (value_) {
 +            if (this.field.type == "selection") {
 +                value_ = _.find(this.field.selection, function (sel) { return sel[0] == value_;});
 +            }
 +            else if (!this.selection.length) {
 +                this.selection = [value_];
 +            }
 +        }
 +        this._super(value_);
 +    },
 +    get_value: function () {
 +        var value = this.get('value');
 +        return value instanceof Array ? value[0] : value;
 +    },
 +    render_value: function () {
 +        var self = this;
 +        this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
 +        this.$("input:checked").prop("checked", false);
 +        if (this.get_value()) {
 +            this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
 +            this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
 +        }
 +    }
 +});
 +
  // jquery autocomplete tweak to allow html and classnames
  (function() {
      var proto = $.ui.autocomplete.prototype,
  })();
  
  /**
 - * A mixin containing some useful methods to handle completion inputs.
 - */
 +    A mixin containing some useful methods to handle completion inputs.
 +    
 +    The widget containing this option can have these arguments in its widget options:
 +    - no_quick_create: if true, it will disable the quick create
 +*/
  instance.web.form.CompletionFieldMixin = {
      init: function() {
          this.limit = 7;
              }
              // quick create
              var raw_result = _(data.result).map(function(x) {return x[1];});
 -            if (search_val.length > 0 && !_.include(raw_result, search_val)) {
 +            if (search_val.length > 0 && !_.include(raw_result, search_val) &&
 +                ! (self.options && (self.options.no_create || self.options.no_quick_create))) {
                  values.push({
                      label: _.str.sprintf(_t('Create "<strong>%s</strong>"'),
                          $('<span />').text(search_val).html()),
                  });
              }
              // create...
 -            values.push({
 -                label: _t("Create and Edit..."),
 -                action: function() {
 -                    self._search_create_popup("form", undefined, self._create_context(search_val));
 -                },
 -                classname: 'oe_m2o_dropdown_option'
 -            });
 +            if (!(self.options && self.options.no_create)){
 +                values.push({
 +                    label: _t("Create and Edit..."),
 +                    action: function() {
 +                        self._search_create_popup("form", undefined, self._create_context(search_val));
 +                    },
 +                    classname: 'oe_m2o_dropdown_option'
 +                });
 +            }
  
              return values;
          });
              self.field.relation,
              {
                  title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string,
 -                initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
 +                initial_ids: ids ? _.map(ids, function(x) {return x[0];}) : undefined,
                  initial_view: view,
                  disable_multiple_selection: true
              },
@@@ -3222,7 -3070,6 +3223,7 @@@ instance.web.form.FieldMany2One = insta
          instance.web.form.CompletionFieldMixin.init.call(this);
          this.set({'value': false});
          this.display_value = {};
 +        this.display_value_backup = {};
          this.last_search = [];
          this.floating = false;
          this.current_display = null;
              );
              pop.on('write_completed', self, function(){
                  self.display_value = {};
 +                self.display_value_backup = {};
                  self.render_value();
                  self.focus();
                  self.trigger('changed_value');
          this.$input.keydown(input_changed);
          this.$input.change(input_changed);
          this.$drop_down.click(function() {
 +            self.$input.focus();
              if (self.$input.autocomplete("widget").is(":visible")) {
 -                self.$input.autocomplete("close");
 -                self.$input.focus();
 +                self.$input.autocomplete("close");                
              } else {
                  if (self.get("value") && ! self.floating) {
                      self.$input.autocomplete("search", "");
                  if (self.last_search.length > 0) {
                      if (self.last_search[0][0] != self.get("value")) {
                          self.display_value = {};
 +                        self.display_value_backup = {};
                          self.display_value["" + self.last_search[0][0]] = self.last_search[0][1];
                          self.reinit_value(self.last_search[0][0]);
                      } else {
                  var item = ui.item;
                  if (item.id) {
                      self.display_value = {};
 +                    self.display_value_backup = {};
                      self.display_value["" + item.id] = item.name;
                      self.reinit_value(item.id);
                  } else if (item.action) {
                  }
                  self.display_value["" + self.get("value")] = data[0][1];
                  self.render_value(true);
 +            }).fail( function (data, event) {
 +                // avoid displaying crash errors as many2One should be name_get compliant
 +                event.preventDefault();
 +                self.display_value["" + self.get("value")] = self.display_value_backup["" + self.get("value")];
 +                self.render_value(true);
              });
          }
      },
          var self = this;
          if (value_ instanceof Array) {
              this.display_value = {};
 +            this.display_value_backup = {};
              if (! this.options.always_reload) {
                  this.display_value["" + value_[0]] = value_[1];
              }
 +            else {
 +                this.display_value_backup["" + value_[0]] = value_[1];
 +            }
              value_ = value_[0];
          }
          value_ = value_ || false;
      },
      add_id: function(id) {
          this.display_value = {};
 +        this.display_value_backup = {};
          this.reinit_value(id);
      },
      is_false: function() {
@@@ -3610,67 -3444,6 +3611,67 @@@ instance.web.form.Many2OneButton = inst
       },
  });
  
 +/**
 + * Abstract-ish ListView.List subclass adding an "Add an item" row to replace
 + * the big ugly button in the header.
 + *
 + * Requires the implementation of a ``is_readonly`` method (usually a proxy to
 + * the corresponding field's readonly or effective_readonly property) to
 + * decide whether the special row should or should not be inserted.
 + *
 + * Optionally an ``_add_row_class`` attribute can be set for the class(es) to
 + * set on the insertion row.
 + */
 +instance.web.form.AddAnItemList = instance.web.ListView.List.extend({
 +    pad_table_to: function (count) {
 +        if (!this.view.is_action_enabled('create') || this.is_readonly()) {
 +            this._super(count);
 +            return;
 +        }
 +
 +        this._super(count > 0 ? count - 1 : 0);
 +
 +        var self = this;
 +        var columns = _(this.columns).filter(function (column) {
 +            return column.invisible !== '1';
 +        }).length;
 +        if (this.options.selectable) { columns++; }
 +        if (this.options.deletable) { columns++; }
 +
 +        var $cell = $('<td>', {
 +            colspan: columns,
 +            'class': this._add_row_class || ''
 +        }).append(
 +            $('<a>', {href: '#'}).text(_t("Add an item"))
 +                .mousedown(function () {
 +                    // FIXME: needs to be an official API somehow
 +                    if (self.view.editor.is_editing()) {
 +                        self.view.__ignore_blur = true;
 +                    }
 +                })
 +                .click(function (e) {
 +                    e.preventDefault();
 +                    e.stopPropagation();
 +                    // FIXME: there should also be an API for that one
 +                    if (self.view.editor.form.__blur_timeout) {
 +                        clearTimeout(self.view.editor.form.__blur_timeout);
 +                        self.view.editor.form.__blur_timeout = false;
 +                    }
 +                    self.view.ensure_saved().done(function () {
 +                        self.view.do_add_record();
 +                    });
 +                }));
 +
 +        var $padding = this.$current.find('tr:not([data-id]):first');
 +        var $newrow = $('<tr>').append($cell);
 +        if ($padding.length) {
 +            $padding.before($newrow);
 +        } else {
 +            this.$current.append($newrow)
 +        }
 +    }
 +});
 +
  /*
  # Values: (0, 0,  { fields })    create
  #         (1, ID, { fields })    update
@@@ -3848,7 -3621,7 +3849,7 @@@ instance.web.form.FieldOne2Many = insta
                   });
                  controller.on('pager_action_executed',self,self.save_any_view);
              } else if (view_type == "graph") {
 -                self.reload_current_view()
 +                self.reload_current_view();
              }
              def.resolve();
          });
      },
      reload_current_view: function() {
          var self = this;
 -        return self.is_loaded = self.is_loaded.then(function() {
 +        self.is_loaded = self.is_loaded.then(function() {
              var active_view = self.viewmanager.active_view;
              var view = self.viewmanager.views[active_view].controller;
              if(active_view === "list") {
                  return view.do_search(self.build_domain(), self.dataset.get_context(), []);
              }
          }, undefined);
 +        return self.is_loaded;
      },
      set_value: function(value_) {
          value_ = value_ || [];
          var self = this;
          this.dataset.reset_ids([]);
 +        var ids;
          if(value_.length >= 1 && value_[0] instanceof Array) {
 -            var ids = [];
 +            ids = [];
              _.each(value_, function(command) {
                  var obj = {values: command[2]};
                  switch (command[0]) {
              this._super(ids);
              this.dataset.set_ids(ids);
          } else if (value_.length >= 1 && typeof(value_[0]) === "object") {
 -            var ids = [];
 +            ids = [];
              this.dataset.delete_all = true;
              _.each(value_, function(command) {
                  var obj = {values: command};
              this.viewmanager.views[this.viewmanager.active_view].controller) {
              var view = this.viewmanager.views[this.viewmanager.active_view].controller;
              if (this.viewmanager.active_view === "form") {
 -                if (!view.is_initialized.state() === 'resolved') {
 +                if (view.is_initialized.state() !== 'resolved') {
                      return $.when(false);
                  }
                  return $.when(view.save());
                  .invoke('is_valid')
                  .all(_.identity)
                  .value();
 -            break;
          case 'list':
              return view.is_valid();
          }
@@@ -4090,7 -3862,7 +4091,7 @@@ instance.web.form.One2ManyListView = in
          var form = editor.form;
          // If no edition is pending, the listview can not be invalid (?)
          if (!editor.record) {
 -            return true
 +            return true;
          }
          // If the form has not been modified, the view can only be valid
          // NB: is_dirty will also be set on defaults/onchanges/whatever?
@@@ -4257,11 -4029,62 +4258,11 @@@ instance.web.form.One2ManyGroups = inst
          }
      }
  });
 -instance.web.form.One2ManyList = instance.web.ListView.List.extend({
 -    pad_table_to: function (count) {
 -        if (!this.view.is_action_enabled('create')) {
 -            this._super(count);
 -        } else {
 -            this._super(count > 0 ? count - 1 : 0);
 -        }
 -
 -        // magical invocation of wtf does that do
 -        if (this.view.o2m.get('effective_readonly')) {
 -            return;
 -        }
 -
 -        var self = this;
 -        var columns = _(this.columns).filter(function (column) {
 -            return column.invisible !== '1';
 -        }).length;
 -        if (this.options.selectable) { columns++; }
 -        if (this.options.deletable) { columns++; }
 -
 -        if (!this.view.is_action_enabled('create')) {
 -            return;
 -        }
 -
 -        var $cell = $('<td>', {
 -            colspan: columns,
 -            'class': 'oe_form_field_one2many_list_row_add'
 -        }).append(
 -            $('<a>', {href: '#'}).text(_t("Add an item"))
 -                .mousedown(function () {
 -                    // FIXME: needs to be an official API somehow
 -                    if (self.view.editor.is_editing()) {
 -                        self.view.__ignore_blur = true;
 -                    }
 -                })
 -                .click(function (e) {
 -                    e.preventDefault();
 -                    e.stopPropagation();
 -                    // FIXME: there should also be an API for that one
 -                    if (self.view.editor.form.__blur_timeout) {
 -                        clearTimeout(self.view.editor.form.__blur_timeout);
 -                        self.view.editor.form.__blur_timeout = false;
 -                    }
 -                    self.view.ensure_saved().done(function () {
 -                        self.view.do_add_record();
 -                    });
 -                }));
 -
 -        var $padding = this.$current.find('tr:not([data-id]):first');
 -        var $newrow = $('<tr>').append($cell);
 -        if ($padding.length) {
 -            $padding.before($newrow);
 -        } else {
 -            this.$current.append($newrow)
 -        }
 -    }
 +instance.web.form.One2ManyList = instance.web.form.AddAnItemList.extend({
 +    _add_row_class: 'oe_form_field_one2many_list_row_add',
 +    is_readonly: function () {
 +        return this.view.o2m.get('effective_readonly');
 +    },
  });
  
  instance.web.form.One2ManyFormView = instance.web.FormView.extend({
@@@ -4291,7 -4114,6 +4292,7 @@@ var lazy_build_o2m_kanban_view = functi
  
  instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
      template: "FieldMany2ManyTags",
 +    tag_template: "FieldMany2ManyTag",
      init: function() {
          this._super.apply(this, arguments);
          instance.web.form.CompletionFieldMixin.init.call(this);
          this._display_orderer = new instance.web.DropMisordered();
          this._drop_shown = false;
      },
 -    initialize_content: function() {
 -        if (this.get("effective_readonly"))
 -            return;
 +    initialize_texttext: function(){
          var self = this;
 -        var ignore_blur = false;
 -        self.$text = this.$("textarea");
 -        self.$text.textext({
 +        return {
              plugins : 'tags arrow autocomplete',
              autocomplete: {
                  render: function(suggestion) {
                          if (data.id) {
                              self.add_id(data.id);
                          } else {
 -                            ignore_blur = true;
 +                            self.ignore_blur = true;
                              data.action();
                          }
                          this.trigger('setSuggestions', {result : []});
                  },
                  core: {
                      onSetInputData: function(e, data) {
 -                        if (data == '') {
 +                        if (data === '') {
                              this._plugins.autocomplete._suggestions = null;
                          }
                          this.input().val(data);
                      },
                  },
              },
 -        }).bind('getSuggestions', function(e, data) {
 +        }
 +    },
 +    initialize_content: function() {
 +        if (this.get("effective_readonly"))
 +            return;
 +        var self = this;
 +        self.ignore_blur = false;
 +        self.$text = this.$("textarea");
 +        self.$text.textext(self.initialize_texttext()).bind('getSuggestions', function(e, data) {
              var _this = this;
              var str = !!data ? data.query || '' : '';
              self.get_search_result(str).done(function(result) {
          self.$text
              .focusin(function () {
                  self.trigger('focused');
 -                ignore_blur = false;
 +                self.ignore_blur = false;
              })
              .focusout(function() {
                  self.$text.trigger("setInputData", "");
 -                if (!ignore_blur) {
 +                if (!self.ignore_blur) {
                      self.trigger('blurred');
                  }
              }).keydown(function(e) {
      get_search_blacklist: function() {
          return this.get("value");
      },
 +    map_tag: function(data){
 +        return _.map(data, function(el) {return {name: el[1], id:el[0]};})
 +    },
 +    get_render_data: function(ids){
 +        var self = this;
 +        var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
 +        return dataset.name_get(ids);
 +    },
 +    render_tag: function(data) {
 +        var self = this;
 +        if (! self.get("effective_readonly")) {
 +            self.tags.containerElement().children().remove();
 +            self.$('textarea').css("padding-left", "3px");
 +            self.tags.addTags(self.map_tag(data));
 +        } else {
 +            self.$el.html(QWeb.render(self.tag_template, {elements: data}));
 +        }
 +    },
      render_value: function() {
          var self = this;
          var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
                  indexed[el[0]] = el;
              });
              data = _.map(values, function(el) { return indexed[el]; });
 -            if (! self.get("effective_readonly")) {
 -                self.tags.containerElement().children().remove();
 -                self.$('textarea').css("padding-left", "3px");
 -                self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};}));
 -            } else {
 -                self.$el.html(QWeb.render("FieldMany2ManyTag", {elements: data}));
 -            }
 -        };
 +            self.render_tag(data);
 +        }
          if (! values || values.length > 0) {
 -            this._display_orderer.add(dataset.name_get(values)).done(handle_names);
 -        } else {
 +            this._display_orderer.add(self.get_render_data(values)).done(handle_names);
 +        }
 +        else{
              handle_names([]);
          }
      },
          var input = this.$text && this.$text[0];
          return input ? input.focus() : false;
      },
 +    set_dimensions: function (height, width) {
 +        this._super(height, width);        
 +        this.$("textarea").css({
 +            width: width,
 +            minHeight: height
 +        });
 +    },    
 +    _search_create_popup: function() {
 +        self.ignore_blur = true;
 +        return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments);
 +    },
  });
  
  /**
@@@ -4495,7 -4289,7 +4496,7 @@@ instance.web.form.FieldMany2Many = inst
          this.$el.addClass('oe_form_field oe_form_field_many2many');
  
          this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, {
 -                    'addable': this.get("effective_readonly") ? null : _t("Add"),
 +                    'addable': null,
                      'deletable': this.get("effective_readonly") ? false : true,
                      'selectable': this.multi_selection,
                      'sortable': false,
@@@ -4562,11 -4356,6 +4563,11 @@@ instance.web.form.Many2ManyDataSet = in
   * @extends instance.web.ListView
   */
  instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{
 +    init: function (parent, dataset, view_id, options) {
 +        this._super(parent, dataset, view_id, _.extend(options || {}, {
 +            ListType: instance.web.form.Many2ManyList,
 +        }));
 +    },
      do_add_record: function () {
          var pop = new instance.web.form.SelectCreatePopup(this);
          pop.select_element(
       },
      is_action_enabled: function () { return true; },
  });
 +instance.web.form.Many2ManyList = instance.web.form.AddAnItemList.extend({
 +    _add_row_class: 'oe_form_field_many2many_list_row_add',
 +    is_readonly: function () {
 +        return this.view.m2m_field.get('effective_readonly');
 +    }
 +});
  
  instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, {
      disable_utility_classes: true,
          if (type !== "form")
              return;
          var self = this;
 +        var pop;
          if (this.dataset.index === null) {
 -            var pop = new instance.web.form.SelectCreatePopup(this);
 +            pop = new instance.web.form.SelectCreatePopup(this);
              pop.select_element(
                  this.field.relation,
                  {
              });
          } else {
              var id = self.dataset.ids[self.dataset.index];
 -            var pop = new instance.web.form.FormOpenPopup(this);
 +            pop = new instance.web.form.FormOpenPopup(this);
              pop.show_element(self.field.relation, id, self.build_context(), {
                  title: _t("Open: ") + self.string,
                  write_function: function(id, data, options) {
@@@ -5062,7 -4844,7 +5063,7 @@@ instance.web.form.SelectCreatePopup = i
                      self.select_elements(self.selected_ids);
                      self.destroy();
                  });
 -                var $cbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-create");
 +                $cbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-create");
                  $cbutton.click(function() {
                      self.new_object();
                  });
@@@ -5147,8 -4929,8 +5148,8 @@@ instance.web.form.FieldReference = inst
          this.selection.on("change:value", this, this.on_selection_changed);
          this.selection.appendTo(this.$(".oe_form_view_reference_selection"));
          this.selection
 -            .on('focused', null, function () {self.trigger('focused')})
 -            .on('blurred', null, function () {self.trigger('blurred')});
 +            .on('focused', null, function () {self.trigger('focused');})
 +            .on('blurred', null, function () {self.trigger('blurred');});
  
          this.m2o = new instance.web.form.FieldMany2One(fm, { attrs: {
              name: 'Referenced Document',
          this.m2o.on("change:value", this, this.data_changed);
          this.m2o.appendTo(this.$(".oe_form_view_reference_m2o"));
          this.m2o
 -            .on('focused', null, function () {self.trigger('focused')})
 -            .on('blurred', null, function () {self.trigger('blurred')});
 +            .on('focused', null, function () {self.trigger('focused');})
 +            .on('blurred', null, function () {self.trigger('blurred');});
      },
      on_selection_changed: function() {
          if (this.reference_ready) {
@@@ -5317,18 -5099,18 +5318,18 @@@ instance.web.form.FieldBinaryFile = ins
          }
      },
      render_value: function() {
 +        var show_value;
          if (!this.get("effective_readonly")) {
              if (this.node.attrs.filename) {
                  show_value = this.view.datarecord[this.node.attrs.filename] || '';
              } else {
 -                show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : '';
 +                show_value = (this.get('value') !== null && this.get('value') !== undefined && this.get('value') !== false) ? this.get('value') : '';
              }
              this.$el.find('input').eq(0).val(show_value);
          } else {
              this.$el.find('a').toggle(!!this.get('value'));
              if (this.get('value')) {
 -                var show_value = _t("Download")
 +                show_value = _t("Download");
                  if (this.view)
                      show_value += " " + (this.view.datarecord[this.node.attrs.filename] || '');
                  this.$el.find('a').text(show_value);
@@@ -5372,13 -5154,6 +5373,13 @@@ instance.web.form.FieldBinaryImage = in
              url = this.placeholder;
          }
          var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url }));
 +        $($img).click(function(e) {
 +            if(self.view.get("actual_mode") == "view") {
 +                var $button = $(".oe_form_button_edit");
 +                $button.openerpBounce();
 +                e.stopPropagation();
 +            }
 +        });
          this.$el.find('> img').remove();
          this.$el.prepend($img);
          $img.load(function() {
  });
  
  /**
 - * Widget for (one2many field) to upload one or more file in same time and display in list.
 + * Widget for (many2many field) to upload one or more file in same time and display in list.
   * The user can delete his files.
   * Options on attribute ; "blockui" {Boolean} block the UI or not
   * during the file is uploading
   */
 -instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
 +instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
      template: "FieldBinaryFileUploader",
      init: function(field_manager, node) {
          this._super(field_manager, node);
          if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') {
              throw _.str.sprintf(_t("The type of the field '%s' must be a many2many field with a relation to 'ir.attachment' model."), this.field.string);
          }
 +        this.data = {};
 +        this.set_value([]);
          this.ds_file = new instance.web.DataSetSearch(this, 'ir.attachment');
          this.fileupload_id = _.uniqueId('oe_fileupload_temp');
          $(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this));
      start: function() {
          this._super(this);
          this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
 +        this.on("change:effective_readonly", this, function () {
 +            this.render_value();
 +        });
      },
      set_value: function(value_) {
 -        var value_ = value_ || [];
 -        var self = this;
 -        var ids = [];
 -        _.each(value_, function(command) {
 -            if (isNaN(command) && command.id == undefined) {
 -                switch (command[0]) {
 -                    case commands.CREATE:
 -                        ids = ids.concat(command[2]);
 -                        return;
 -                    case commands.REPLACE_WITH:
 -                        ids = ids.concat(command[2]);
 -                        return;
 -                    case commands.UPDATE:
 -                        ids = ids.concat(command[2]);
 -                        return;
 -                    case commands.LINK_TO:
 -                        ids = ids.concat(command[1]);
 -                        return;
 -                    case commands.DELETE:
 -                        ids = _.filter(ids, function (id) { return id != command[1];});
 -                        return;
 -                    case commands.DELETE_ALL:
 -                        ids = [];
 -                        return;
 -                }
 -            } else {
 -                ids.push(command);
 -            }
 -        });
 -        this._super( ids );
 +        value_ = value_ || [];
 +        if (value_.length >= 1 && value_[0] instanceof Array) {
 +            value_ = value_[0][2];
 +        }
 +        this._super(value_);
      },
      get_value: function() {
 -        return _.map(this.get('value'), function (value) { return commands.link_to( isNaN(value) ? value.id : value ); });
 +        var tmp = [commands.replace_with(this.get("value"))];
 +        return tmp;
      },
      get_file_url: function (attachment) {
          return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']});
      },
      read_name_values : function () {
          var self = this;
 -        // select the list of id for a get_name
 -        var values = [];
 -        _.each(this.get('value'), function (val) {
 -            if (typeof val != 'object') {
 -                values.push(val);
 -            }
 -        });
 +        // don't reset know values
 +        var ids = this.get('value');
 +        var _value = _.filter(ids, function (id) { return typeof self.data[id] == 'undefined'; } );
          // send request for get_name
 -        if (values.length) {
 -            return this.ds_file.call('read', [values, ['id', 'name', 'datas_fname']]).done(function (datas) {
 +        if (_value.length) {
 +            return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname']]).then(function (datas) {
                  _.each(datas, function (data) {
                      data.no_unlink = true;
                      data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id});
 -
 -                    _.each(self.get('value'), function (val, key) {
 -                        if(val == data.id) {
 -                            self.get('value')[key] = data;
 -                        }
 -                    });
 +                    self.data[data.id] = data;
                  });
 +                return ids;
              });
          } else {
 -            return $.when(this.get('value'));
 +            return $.when(ids);
          }
      },
      render_value: function () {
          var self = this;
 -        this.read_name_values().then(function (datas) {
 -
 -            var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self}));
 +        this.$('.oe_add').css('visibility', this.get('effective_readonly') ? 'hidden': '');
 +        this.read_name_values().then(function (ids) {
 +            var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}));
              render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
              self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
  
          var self = this;
          var $target = $(event.target);
          if ($target.val() !== '') {
 -
              var filename = $target.val().replace(/.*[\\\/]/,'');
 -
 -            // if the files is currently uploded, don't send again
 -            if( !isNaN(_.find(this.get('value'), function (file) { return (file.filename || file.name) == filename && file.upload; } )) ) {
 +            // don't uplode more of one file in same time
 +            if (self.data[0] && self.data[0].upload ) {
                  return false;
              }
 +            for (var id in this.get('value')) {
 +                // if the files exits, delete the file before upload (if it's a new file)
 +                if (self.data[id] && (self.data[id].filename || self.data[id].name) == filename && !self.data[id].no_unlink ) {
 +                    self.ds_file.unlink([id]);
 +                }
 +            }
  
              // block UI or not
              if(this.node.attrs.blockui>0) {
                  instance.web.blockUI();
              }
  
 -            // if the files exits for this answer, delete the file before upload
 -            var files = _.filter(this.get('value'), function (file) {
 -                if((file.filename || file.name) == filename) {
 -                    self.ds_file.unlink([file.id]);
 -                    return false;
 -                } else {
 -                    return true;
 -                }
 -            });
 -
              // TODO : unactivate send on wizard and form
  
              // submit file
              this.$('form.oe_form_binary_form').submit();
              this.$(".oe_fileupload").hide();
 -
 -            // add file on result
 -            files.push({
 +            // add file on data result
 +            this.data[0] = {
                  'id': 0,
                  'name': filename,
                  'filename': filename,
                  'url': '',
                  'upload': true
 -            });
 -
 -            this.set({'value': files});
 +            };
          }
      },
      on_file_loaded: function (event, result) {
              instance.web.unblockUI();
          }
  
 -        // TODO : activate send on wizard and form
 -
          if (result.error || !result.id ) {
              this.do_warn( _t('Uploading Error'), result.error);
 -            files = _.filter(files, function (val) { return !val.upload; });
 +            delete this.data[0];
          } else {
 -            for(var i in files){
 -                if(files[i].filename == result.filename && files[i].upload) {
 -                    files[i] = {
 -                        'id': result.id,
 -                        'name': result.name,
 -                        'filename': result.filename,
 -                        'url': this.get_file_url(result)
 -                    };
 -                }
 +            if (this.data[0] && this.data[0].filename == result.filename && this.data[0].upload) {
 +                delete this.data[0];
 +                this.data[result.id] = {
 +                    'id': result.id,
 +                    'name': result.name,
 +                    'filename': result.filename,
 +                    'url': this.get_file_url(result)
 +                };
 +            } else {
 +                this.data[result.id] = {
 +                    'id': result.id,
 +                    'name': result.name,
 +                    'filename': result.filename,
 +                    'url': this.get_file_url(result)
 +                };
              }
 +            var values = _.clone(this.get('value'));
 +            values.push(result.id);
 +            this.set({'value': values});
          }
 -
 -        this.set({'value': files});
 -        this.render_value()
 +        this.render_value();
      },
      on_file_delete: function (event) {
          event.stopPropagation();
          var file_id=$(event.target).data("id");
          if (file_id) {
 -            var files=[];
 -            for(var i in this.get('value')){
 -                if(file_id != this.get('value')[i].id){
 -                    files.push(this.get('value')[i]);
 -                }
 -                else if(!this.get('value')[i].no_unlink) {
 -                    this.ds_file.unlink([file_id]);
 -                }
 +            var files = _.filter(this.get('value'), function (id) {return id != file_id;});
 +            if(!this.data[file_id].no_unlink) {
 +                this.ds_file.unlink([file_id]);
              }
              this.set({'value': files});
          }
@@@ -5574,10 -5385,9 +5575,10 @@@ instance.web.form.FieldStatus = instanc
          this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false;
          this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false;
          this.set({value: false});
 -        this.selection = [];
 -        this.set("selection", []);
 +        this.selection = {'unfolded': [], 'folded': []};
 +        this.set("selection", {'unfolded': [], 'folded': []});
          this.selection_dm = new instance.web.DropMisordered();
 +        this.dataset = new instance.web.DataSetStatic(this, this.field.relation, this.build_context());
      },
      start: function() {
          this.field_manager.on("view_content_has_changed", this, this.calc_domain);
          });
          this.get_selection();
          if (this.options.clickable) {
 -            this.$el.on('click','li',this.on_click_stage);
 +            this.$el.on('click','li[data-id]',this.on_click_stage);
 +        }
 +        if (this.$el.parent().is('header')) {
 +            this.$el.after('<div class="oe_clear"/>');
          }
          this._super();
      },
      },
      render_value: function() {
          var self = this;
 -        var content = QWeb.render("FieldStatus.content", {widget: self});
 +        var content = QWeb.render("FieldStatus.content", {
 +            'widget': self, 
 +            'value_folded': _.find(self.selection.folded, function(i){return i[0] === self.get('value');})
 +        });
          self.$el.html(content);
 -        var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}");
 -        var color = colors[self.get('value')];
 -        if (color) {
 -            self.$("oe_active").css("color", color);
 -        }
      },
      calc_domain: function() {
          var d = instance.web.pyeval.eval('domain', this.build_domain());
          var domain = []; //if there is no domain defined, fetch all the records
 -        
 +
          if (d.length) {
              domain = ['|',['id', '=', this.get('value')]].concat(d);
          }
 -        
 +
          if (! _.isEqual(domain, this.get("evaluated_selection_domain"))) {
              this.set("evaluated_selection_domain", domain);
          }
       */
      get_selection: function() {
          var self = this;
 -        var selection = [];
 +        var selection_unfolded = [];
 +        var selection_folded = [];
 +        var fold_field = this.options.fold_field;
  
          var calculation = _.bind(function() {
              if (this.field.type == "many2one") {
 -                var domain = [];
 -                var ds = new instance.web.DataSetSearch(this, this.field.relation,
 -                    self.build_context(), this.get("evaluated_selection_domain"));
 -                return ds.read_slice(['name'], {}).then(function (records) {
 -                    for(var i = 0; i < records.length; i++) {
 -                        selection.push([records[i].id, records[i].name]);
 -                    }
 -                });
 +                return self.get_distant_fields().then(function (fields) {
 +                    return new instance.web.DataSetSearch(self, self.field.relation, self.build_context(), self.get("evaluated_selection_domain"))
 +                        .read_slice(_.union(_.keys(self.distant_fields), ['id']), {}).then(function (records) {
 +                            var ids = _.pluck(records, 'id');
 +                            return self.dataset.name_get(ids).then(function (records_name) {
 +                                _.each(records, function (record) {
 +                                    var name = _.find(records_name, function (val) {return val[0] == record.id;})[1];
 +                                    if (fold_field && record[fold_field] && record.id != self.get('value')) {
 +                                        selection_folded.push([record.id, name]);
 +                                    } else {
 +                                        selection_unfolded.push([record.id, name]);
 +                                    }
 +                                });
 +                            });
 +                        });
 +                    });
              } else {
                  // For field type selection filter values according to
                  // statusbar_visible attribute of the field. For example:
                  for(var i=0; i < select.length; i++) {
                      var key = select[i][0];
                      if(key == this.get('value') || !this.options.visible || this.options.visible.indexOf(key) != -1) {
 -                        selection.push(select[i]);
 +                        selection_unfolded.push(select[i]);
                      }
                  }
                  return $.when();
              }
          }, this);
          this.selection_dm.add(calculation()).then(function () {
 +            var selection = {'unfolded': selection_unfolded, 'folded': selection_folded};
              if (! _.isEqual(selection, self.get("selection"))) {
                  self.set("selection", selection);
              }
          });
      },
 +    /*
 +     * :deprecated: this feature will probably be removed with OpenERP v8
 +     */
 +    get_distant_fields: function() {
 +        var self = this;
 +        if (! this.options.fold_field) {
 +            this.distant_fields = {}
 +        }
 +        if (this.distant_fields) {
 +            return $.when(this.distant_fields);
 +        }
 +        return new instance.web.Model(self.field.relation).call("fields_get", [[this.options.fold_field]]).then(function(fields) {
 +            self.distant_fields = fields;
 +            return fields;
 +        });
 +    },
      on_click_stage: function (ev) {
          var self = this;
          var $li = $(ev.currentTarget);
 -        var val = parseInt($li.data("id"));
 +        var val;
 +        if (this.field.type == "many2one") {
 +            val = parseInt($li.data("id"), 10);
 +        }
 +        else {
 +            val = $li.data("id");
 +        }
          if (val != self.get('value')) {
              this.view.recursive_save().done(function() {
                  var change = {};
@@@ -5750,131 -5526,6 +5751,131 @@@ instance.web.form.FieldMonetary = insta
      },
  });
  
 +/*
 +    This type of field display a list of checkboxes. It works only with m2ms. This field will display one checkbox for each
 +    record existing in the model targeted by the relation, according to the given domain if one is specified. Checked records
 +    will be added to the relation.
 +*/
 +instance.web.form.FieldMany2ManyCheckBoxes = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
 +    className: "oe_form_many2many_checkboxes",
 +    init: function() {
 +        this._super.apply(this, arguments);
 +        this.set("value", {});
 +        this.set("records", []);
 +        this.field_manager.on("view_content_has_changed", this, function() {
 +            var domain = new openerp.web.CompoundDomain(this.build_domain()).eval();
 +            if (! _.isEqual(domain, this.get("domain"))) {
 +                this.set("domain", domain);
 +            }
 +        });
 +        this.records_orderer = new instance.web.DropMisordered();
 +    },
 +    initialize_field: function() {
 +        instance.web.form.ReinitializeFieldMixin.initialize_field.call(this);
 +        this.on("change:domain", this, this.query_records);
 +        this.set("domain", new openerp.web.CompoundDomain(this.build_domain()).eval());
 +        this.on("change:records", this, this.render_value);
 +    },
 +    query_records: function() {
 +        var self = this;
 +        var model = new openerp.Model(openerp.session, this.field.relation);
 +        this.records_orderer.add(model.call("search", [this.get("domain")], {"context": this.build_context()}).then(function(record_ids) {
 +            return model.call("name_get", [record_ids] , {"context": self.build_context()});
 +        })).then(function(res) {
 +            self.set("records", res);
 +        });
 +    },
 +    render_value: function() {
 +        this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this, selected: this.get("value")}));
 +        var inputs = this.$("input");
 +        inputs.change(_.bind(this.from_dom, this));
 +        if (this.get("effective_readonly"))
 +            inputs.attr("disabled", "true");
 +    },
 +    from_dom: function() {
 +        var new_value = {};
 +        this.$("input").each(function() {
 +            var elem = $(this);
 +            new_value[elem.data("record-id")] = elem.attr("checked") ? true : undefined;
 +        });
 +        if (! _.isEqual(new_value, this.get("value")))
 +            this.internal_set_value(new_value);
 +    },
 +    set_value: function(value) {
 +        value = value || [];
 +        if (value.length >= 1 && value[0] instanceof Array) {
 +            value = value[0][2];
 +        }
 +        var formatted = {};
 +        _.each(value, function(el) {
 +            formatted[JSON.stringify(el)] = true;
 +        });
 +        this._super(formatted);
 +    },
 +    get_value: function() {
 +        var value = _.filter(_.keys(this.get("value")), function(el) {
 +            return this.get("value")[el];
 +        }, this);
 +        value = _.map(value, function(el) {
 +            return JSON.parse(el);
 +        });
 +        return [commands.replace_with(value)];
 +    },
 +});
 +
 +/**
 +    This field can be applied on many2many and one2many. It is a read-only field that will display a single link whose name is
 +    "<number of linked records> <label of the field>". When the link is clicked, it will redirect to another act_window
 +    action on the model of the relation and show only the linked records.
 +
 +    Widget options:
 +
 +    * views: The views to display in the act_window action. Must be a list of tuples whose first element is the id of the view
 +      to display (or False to take the default one) and the second element is the type of the view. Defaults to
 +      [[false, "tree"], [false, "form"]] .
 +*/
 +instance.web.form.X2ManyCounter = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
 +    className: "oe_form_x2many_counter",
 +    init: function() {
 +        this._super.apply(this, arguments);
 +        this.set("value", []);
 +        _.defaults(this.options, {
 +            "views": [[false, "tree"], [false, "form"]],
 +        });
 +    },
 +    render_value: function() {
 +        var text = _.str.sprintf("%d %s", this.val().length, this.string);
 +        this.$().html(QWeb.render("X2ManyCounter", {text: text}));
 +        this.$("a").click(_.bind(this.go_to, this));
 +    },
 +    go_to: function() {
 +        return this.view.recursive_save().then(_.bind(function() {
 +            var val = this.val();
 +            var context = {};
 +            if (this.field.type === "one2many") {
 +                context["default_" + this.field.relation_field] = this.view.datarecord.id;
 +            }
 +            var domain = [["id", "in", val]];
 +            return this.do_action({
 +                type: 'ir.actions.act_window',
 +                name: this.string,
 +                res_model: this.field.relation,
 +                views: this.options.views,
 +                target: 'current',
 +                context: context,
 +                domain: domain,
 +            });
 +        }, this));
 +    },
 +    val: function() {
 +        var value = this.get("value") || [];
 +        if (value.length >= 1 && value[0] instanceof Array) {
 +            value = value[0][2];
 +        }
 +        return value;
 +    }
 +});
 +
  /**
   * Registry of form fields, called by :js:`instance.web.FormView`.
   *
@@@ -5891,7 -5542,6 +5892,7 @@@ instance.web.form.widgets = new instanc
      'date' : 'instance.web.form.FieldDate',
      'datetime' : 'instance.web.form.FieldDatetime',
      'selection' : 'instance.web.form.FieldSelection',
 +    'radio' : 'instance.web.form.FieldRadio',
      'many2one' : 'instance.web.form.FieldMany2One',
      'many2onebutton' : 'instance.web.form.Many2OneButton',
      'many2many' : 'instance.web.form.FieldMany2Many',
      'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles',
      'statusbar': 'instance.web.form.FieldStatus',
      'monetary': 'instance.web.form.FieldMonetary',
 +    'many2many_checkboxes': 'instance.web.form.FieldMany2ManyCheckBoxes',
 +    'x2many_counter': 'instance.web.form.X2ManyCounter',
  });
  
  /**
@@@ -5927,6 -5575,6 +5928,6 @@@ instance.web.form.tags = new instance.w
  instance.web.form.custom_widgets = new instance.web.Registry({
  });
  
 -};
 +})();
  
  // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
@@@ -2,10 -2,7 +2,10 @@@
   * handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out
   * @namespace
   */
 -openerp.web.list_editable = function (instance) {
 +(function() {
 +
 +    var instance = openerp;
 +    openerp.web.list_editable = {};
      var _t = instance.web._t;
  
      // editability status of list rows
          make_empty_record: function (id) {
              var attrs = {id: id};
              _(this.columns).chain()
 -                .filter(function (x) { return x.tag === 'field'})
 +                .filter(function (x) { return x.tag === 'field';})
                  .pluck('name')
                  .each(function (field) { attrs[field] = false; });
              return new instance.web.list.Record(attrs);
                      }, options).then(function () {
                          $recordRow.addClass('oe_edition');
                          self.resize_fields();
-                         var focus_field = options && options.focus_field ? options.focus_field : (self.visible_columns.length ? self.visible_columns[0].name : undefined);
+                         var focus_field = options && options.focus_field ? options.focus_field : undefined;
+                         if (!focus_field){
+                             focus_field = _.find(self.editor.form.fields_order, function(field){ return fields[field] && fields[field].$el.is(':visible:has(input)'); });
+                         }
                          if (focus_field) fields[focus_field].$el.find('input').select();
                          return record.attributes;
                      });
          get_cells_for: function ($row) {
              var cells = {};
              $row.children('td').each(function (index, el) {
 -                cells[el.getAttribute('data-field')] = el
 +                cells[el.getAttribute('data-field')] = el;
              });
              return cells;
          },
                          var record = self.records.get(attrs.id);
                          if (!record) {
                              // Record removed by third party during edition
 -                            return
 +                            return;
                          }
                          return self.reload_record(record);
                      }
                  };
              } else if (document.body.createTextRange) {
                  throw new Error("Implement text range handling for MSIE");
 -                var sel = document.body.createTextRange();
 -                if (sel.parentElement() === el) {
 -
 -                }
              }
              // Element without selection ranges (select, div/@contenteditable)
              return null;
              var arch = edition_view.arch;
              if (!(arch && arch.children instanceof Array)) {
                  throw new Error("Editor delegate's #edition_view must have a" +
 -                                " non-empty arch")
 +                                " non-empty arch");
              }
 -            if (!(arch.tag === "form")) {
 +            if (arch.tag !== "form") {
                  throw new Error("Editor delegate's #edition_view must have a" +
                                  " 'form' root node");
              }
              return null;
          }
      });
 -};
 +})();