[MERGE] forward port of branch 7.0 up to revid 4067 chs@openerp.com-20131114142639... saas-1
authorChristophe Simonis <chs@openerp.com>
Fri, 15 Nov 2013 10:49:09 +0000 (11:49 +0100)
committerChristophe Simonis <chs@openerp.com>
Fri, 15 Nov 2013 10:49:09 +0000 (11:49 +0100)
bzr revid: dle@openerp.com-20131112134311-h1vsux0ge17bsqkc
bzr revid: chs@openerp.com-20131114134731-n324awyon0spq624
bzr revid: chs@openerp.com-20130823145204-xwpnlwg0gg2259f6
bzr revid: chs@openerp.com-20130906170157-e7m4pjskyi47q82o
bzr revid: dle@openerp.com-20130909170408-wxgoduzggap6o4ng
bzr revid: dle@openerp.com-20130919141212-ridtrvvfwvu6calr
bzr revid: dle@openerp.com-20131018120136-fvoq337kgx74njsy
bzr revid: dle@openerp.com-20131023103308-18pj2gqq3imrcir7
bzr revid: chs@openerp.com-20131030180528-hqsztaujjjqev8ky
bzr revid: dle@openerp.com-20131106100128-mx8mnguvp321wick
bzr revid: chs@openerp.com-20131115104909-3u3mu40g9xnler88

1  2 
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/search.js
addons/web/static/src/js/view_form.js
addons/web/static/src/xml/base.xml
addons/web_kanban/static/src/css/kanban.css
addons/web_kanban/static/src/css/kanban.sass
addons/web_kanban/static/src/js/kanban.js
openerp/osv/orm.py

    background: #7c7bad;
    color: #eeeeee;
  }
 +.openerp .oe_form_field_radio.oe_horizontal {
 +  white-space: nowrap;
 +}
 +.openerp .oe_form_field_radio.oe_horizontal label {
 +  display: inline-block;
 +  text-align: center;
 +  height: 16px;
 +}
 +.openerp .oe_form_field_radio.oe_vertical label {
 +  margin-left: 4px;
 +}
 +.openerp .oe_form_field_radio.oe_form_required .oe_radio_input {
 +  border: 2px solid transparent;
 +  display: inline-block;
 +  height: 12px;
 +  width: 12px;
 +  vertical-align: bottom;
 +  border-radius: 10px;
 +  margin: 1px 0;
 +}
 +.openerp .oe_form_field_radio.oe_form_required.oe_form_invalid .oe_radio_input {
 +  border-color: red;
 +}
  .openerp .oe_tags {
    margin-bottom: 1px;
  }
    display: block;
    color: #4c4c4c;
    text-decoration: none;
-   width: 200px;
-   text-overflow: ellipsis;
-   overflow: hidden;
  }
  .openerp .oe_dropdown_menu > li > a:hover {
    text-decoration: none;
    display: table-row;
    height: inherit;
  }
- .openerp .oe_view_manager .oe_view_manager_view_kanban {
+ .openerp .oe_view_manager .oe_view_manager_view_kanban:not(:empty) {
    height: inherit;
  }
  .openerp .oe_view_manager table.oe_view_manager_header {
  .openerp .oe_form_invisible {
    display: none !important;
  }
 +.openerp .oe_form_editable .oe_read_only {
 +  display: none !important;
 +}
  .openerp .oe_form_readonly .oe_edit_only, .openerp .oe_form_readonly .oe_form_field:empty {
    display: none !important;
  }
    background: white;
    min-width: 60px;
    color: #1f1f1f;
 +  font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
  }
  .openerp .oe_form input[readonly], .openerp .oe_form select[readonly], .openerp .oe_form textarea[readonly], .openerp .oe_form input[disabled], .openerp .oe_form select[disabled] {
    background: #e5e5e5 !important;
    -moz-border-radius: 0px;
    -webkit-border-radius: 0px;
    border-radius: 0px;
 +  color: #4c4c4c;
  }
  .openerp .oe_form textarea.oe_inline[disabled] {
    border-left: 8px solid #eeeeee;
    white-space: nowrap;
  }
  .openerp .oe_form .oe_form_field_boolean {
 -  padding-top: 4px;
    width: auto;
  }
  .openerp .oe_form .oe_datepicker_container {
    color: #333333;
  }
  
 +@-moz-document url-prefix() {
 +  .openerp .oe_view_manager .oe_view_manager_switch li {
 +    line-height: 21px;
 +  }
 +  .openerp .oe_searchview .oe_searchview_search {
 +    top: -1px;
 +  }
 +  .openerp .oe_form_field_many2one .oe_m2o_cm_button {
 +    line-height: 18px;
 +  }
 +  .openerp .oe_secondary_submenu {
 +    line-height: 14px;
 +  }
 +  .openerp .oe_webclient .oe_star_on, .openerp .oe_webclient .oe_star_off {
 +    top: 0px;
 +  }
 +}
 +
  .kitten-mode-activated {
    background-size: cover;
    background-attachment: fixed;
    opacity: 0.7;
  }
  
 +.loading-kitten {
 +  -moz-border-radius: 15px;
 +  -webkit-border-radius: 15px;
 +  border-radius: 15px;
 +  -moz-box-shadow: 0 0 5px 5px #999999;
 +  -webkit-box-shadow: 0 0 5px 5px #999999;
 +  box-shadow: 0 0 5px 5px #999999;
 +}
 +
  div.ui-widget-overlay {
    background: black;
    filter: alpha(opacity=30);
@@@ -482,28 -482,7 +482,28 @@@ $sheet-padding: 16p
      .oe_tag_dark
          background: $tag-bg-dark
          color: #eee
 -
 +    .oe_form_field_radio
 +        &.oe_horizontal
 +            white-space: nowrap
 +            label
 +                display: inline-block
 +                text-align: center
 +                height: 16px
 +        &.oe_vertical
 +            label
 +                margin-left: 4px
 +        &.oe_form_required
 +            .oe_radio_input
 +                border: 2px solid transparent
 +                display: inline-block
 +                height: 12px
 +                width: 12px
 +                vertical-align: bottom
 +                border-radius: 10px
 +                margin: 1px 0
 +            &.oe_form_invalid
 +                .oe_radio_input
 +                    border-color: red
      .oe_tags
          &.oe_inline
              min-width: 250px
                  display: block
                  color: #4c4c4c
                  text-decoration: none
-                 width: 200px
-                 text-overflow: ellipsis
-                 overflow: hidden
                  &:hover
                      text-decoration: none
      .oe_dropdown_arrow:after
          .oe_view_manager_body
              display: table-row
              height: inherit
-         .oe_view_manager_view_kanban
+         .oe_view_manager_view_kanban:not(:empty)
              height: inherit
  
          table.oe_view_manager_header
              @include box-shadow((0 1px 2px rgba(0, 0, 0, .1), 0 1px 1px rgba(255, 255, 255, .8) inset))
      .oe_form_invisible
          display: none !important
 +    .oe_form_editable
 +        .oe_read_only
 +            display: none !important
      .oe_form_readonly
          .oe_edit_only, .oe_form_field:empty
              display: none !important
              background: white
              min-width: 60px
              color: #1f1f1f
 +            font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif
          input[readonly], select[readonly], textarea[readonly], input[disabled], select[disabled]
              background: #E5E5E5 !important
              color: #666
              padding-left: 8px
              @include box-shadow(none)
              @include radius(0px)
 +            color: #4c4c4c
          textarea.oe_inline[disabled]
              border-left: 8px solid #eee
          .oe_form_field_url button img
          .oe_form_field_datetime
              white-space: nowrap
          .oe_form_field_boolean
 -            padding-top: 4px
              width: auto
          .oe_datepicker_container
              display: none
          float: right
          color: #333
      // }}}
 +@-moz-document url-prefix()
 +    .openerp
 +        .oe_view_manager .oe_view_manager_switch li
 +            line-height: 21px
 +        .oe_searchview .oe_searchview_search
 +            top: -1px
 +        .oe_form_field_many2one .oe_m2o_cm_button
 +            line-height: 18px
 +        .oe_secondary_submenu
 +            line-height: 14px
 +        .oe_webclient
 +            .oe_star_on, .oe_star_off
 +                top: 0px
 +
  // Kitten Mode {{{
  .kitten-mode-activated
      background-size: cover
      background-attachment: fixed
      >*
          opacity: 0.70
 +.loading-kitten
 +    @include radius(15px)
 +    @include box-shadow(0 0 5px 5px #999)
  // }}}
  
  // jQueryUI top level {{{
@@@ -357,6 -357,13 +357,6 @@@ instance.web.SearchView = instance.web.
       * @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,
@@@ -1540,7 -1547,7 +1540,7 @@@ instance.web.search.ManyToOneField = in
              context: context
          }).then(function (results) {
              if (_.isEmpty(results)) { return null; }
-             return [{label: _.escape(self.attrs.string)}].concat(
+             return [{label: self.attrs.string}].concat(
                  _(results).map(function (result) {
                      return {
                          label: _.escape(result[1]),
@@@ -1726,7 -1733,10 +1726,10 @@@ instance.web.search.CustomFilters = ins
          var $name = this.$('input:first');
          var private_filter = !this.$('#oe_searchview_custom_public').prop('checked');
          var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
+         if (_.isEmpty($name.val())){
+             this.do_warn(_t("Error"), _t("Filter name is required."));
+             return false;
+         }
          var search = this.view.build_search_data();
          instance.web.pyeval.eval_domains_and_contexts({
              domains: search.domains,
@@@ -2862,99 -2862,6 +2862,99 @@@ instance.web.form.FieldSelection = inst
      }
  });
  
 +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,
@@@ -3027,7 -2934,7 +3027,7 @@@ instance.web.form.CompletionFieldMixin 
                  values.push({
                      label: _t("Search More..."),
                      action: function() {
-                         dataset.name_search(search_val, self.build_domain(), 'ilike', false).done(function(data) {
+                         dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(data) {
                              self._search_create_popup("search", data);
                          });
                      },
@@@ -3154,7 -3061,6 +3154,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.view.do_onchange(self);
          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) {
              this.alive(dataset.name_get([self.get("value")])).done(function(data) {
                  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() {
@@@ -3649,7 -3542,7 +3649,7 @@@ instance.web.form.FieldOne2Many = insta
                  _.extend(view.options, {
                      addable: null,
                      selectable: self.multi_selection,
-                     sortable: false,
+                     sortable: true,
                      import_enabled: false,
                      deletable: true
                  });
@@@ -4044,7 -3937,13 +4044,13 @@@ instance.web.form.One2ManyListView = in
              else
                  return $.when();
          }).done(function () {
-             self.handle_button(name, id, callback);
+             if (!self.o2m.options.reload_on_button) {
+                 self.handle_button(name, id, callback);
+             }else {
+                 self.handle_button(name, id, function(){
+                     self.o2m.view.reload();
+                 });
+             }
          });
      },
  
@@@ -4339,13 -4238,6 +4345,13 @@@ instance.web.form.FieldMany2ManyTags = 
          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
 +        });
 +    },
  });
  
  /**
@@@ -5269,7 -5161,7 +5275,7 @@@ instance.web.form.FieldBinaryImage = in
  });
  
  /**
 - * 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
@@@ -5283,8 -5175,6 +5289,8 @@@ instance.web.form.FieldMany2ManyBinaryM
          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));
          this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
      },
      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 _value = _.filter(this.get('value'), 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']]).done(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;
                  });
              });
          } else {
 -            return $.when(this.get('value'));
 +            return $.when();
          }
      },
      render_value: function () {
          var self = this;
 -        this.read_name_values().then(function (datas) {
 +        this.read_name_values().then(function () {
  
              var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self}));
              render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
          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()
      },
      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});
          }
@@@ -5462,6 -5395,11 +5468,6 @@@ instance.web.form.FieldStatus = instanc
          var self = this;
          var content = QWeb.render("FieldStatus.content", {widget: self});
          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());
@@@ -5585,7 -5523,6 +5591,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',
@@@ -40,7 -40,7 +40,7 @@@
          <td>
              <p>
                  <t t-js="d">
 -                    var message = d.message ? d.message : d.error.data.fault_code;
 +                    var message = d.message ? d.message : d.error.data.message;
                      d.html_error = context.engine.tools.html_escape(message)
                          .replace(/\n/g, '<br/>');
                  </t>
  <tr t-name="TreeView.rows"
          t-foreach="records" t-as="record"
          t-att-id="'treerow_' + record.id"
-         t-att-data-id="record.id" t-att-data-level="level + 1">
+         t-att-data-id="record.id" t-att-data-level="level + 1"
+         t-att-data-row-parent-id="row_parent_id">
      <t t-set="children" t-value="record[children_field]"/>
      <t t-set="class" t-value="children and children.length ? 'treeview-tr' : 'treeview-td'"/>
      <t t-set="rank" t-value="'oe-treeview-first'"/>
          </select>
      </span>
  </t>
 +<t t-name="FieldRadio">
 +    <span t-attf-class="oe_form_field oe_form_field_radio #{widget.options.horizontal ? 'oe_horizontal' : 'oe_vertical'}" t-att-style="widget.node.attrs.style">
 +        <span t-if="!widget.get('effective_readonly')">
 +            <t t-if="widget.options.horizontal">
 +                <t t-set="width" t-value="Math.floor(100 / widget.selection.length)"/>
 +                <t t-if="!widget.options.no_radiolabel">
 +                    <t t-foreach="widget.selection" t-as="selection">
 +                        <label t-att-for="widget.uniqueId + '_' + selection[0]" t-att-style="'width: ' + width + '%;'"><t t-esc="selection[1]"/></label>
 +                    </t>
 +                    <br/>
 +                </t>
 +                <t t-foreach="widget.selection" t-as="selection">
 +                    <div t-att-style="'width: ' + width + '%;'">
 +                        <span class="oe_radio_input"><input type="radio" t-att-name="widget.uniqueId" t-att-id="widget.uniqueId + '_' + selection[0]" t-att-value="selection[0]"/></span>
 +                    </div>
 +                </t>
 +            </t>
 +            <t t-if="!widget.options.horizontal">
 +                <t t-foreach="widget.selection" t-as="selection">
 +                    <div>
 +                        <span class="oe_radio_input"><input type="radio" t-att-id="widget.uniqueId + '_' + selection[0]" t-att-name="widget.uniqueId" t-att-value="selection[0]"/></span><label t-if="!widget.options.no_radiolabel" t-att-for="widget.uniqueId + '_' + selection[0]"><t t-esc="selection[1]"/></label>
 +                    </div>
 +                </t>
 +            </t>
 +        </span>
 +        <span t-if="widget.get('effective_readonly')" class="oe_radio_readonly"><t t-esc="widget.get('value')[1]"/></span>
 +    </span>
 +</t>
  <t t-name="FieldMany2One">
      <span class="oe_form_field oe_form_field_many2one oe_form_field_with_button" t-att-style="widget.node.attrs.style">
          <t t-if="widget.get('effective_readonly')">
  </t>
  <t t-name="FieldBinaryFileUploader.files">
      <div class="oe_attachments">
 -        <t t-if="widget.get('value')">
 -            <t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
 +        <t t-if="!widget.get('effective_readonly')">
 +            <t t-foreach="widget.get('value')" t-as="id">
 +                <t t-set="file" t-value="widget.data[id]"/>
                  <div class="oe_attachment">
                      <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
                          <span class="oe_fileuploader_in_process">...Upload in progress...</span>
                      </t>
                  </div>
              </t>
 -            <t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
 +        </t>
 +        <t t-if="widget.get('effective_readonly')">
 +            <t t-foreach="widget.get('value')" t-as="id">
 +                <t t-set="file" t-value="widget.data[id]"/>
                  <div>
                      <a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
                          <t t-raw="file.name || file.filename"/>
          <ul class="oe_searchview_custom_list"/>
          <div class="oe_searchview_custom">
              <h4>Save current filter</h4>
-             <form>
-                 <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
+             <form class="oe_form">
+                 <p class="oe_form_required"><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
                  <p>
                      <input id="oe_searchview_custom_public" type="checkbox"/>
                      <label for="oe_searchview_custom_public">Share with all users</label>
  @charset "utf-8";
  .openerp .oe_kanban_view {
    background: white;
 -  height: inherit;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_dummy_cell {
 -  background: url(/web/static/src/img/form_sheetbg.png);
 -  width: 100%;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_length {
 -  text-align: center;
 -  display: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_length .oe_tag {
 -  position: relative;
 -  top: 8px;
 -  font-weight: bold;
 -}
 -.openerp .oe_kanban_view .oe_kanban_header:hover .oe_kanban_group_length {
 -  display: none;
 -}
 -.openerp .oe_kanban_view .ui-sortable-placeholder {
 -  border: 1px solid rgba(0, 0, 0, 0.1);
 -  visibility: visible !important;
 -}
 -.openerp .oe_kanban_view .ui-sortable-helper {
 -  -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
 -  -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
 -  -box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
 -  -moz-transform: rotate(3deg);
 -  -webkit-transform: rotate(3deg);
 -  -o-transform: rotate(3deg);
 -  -ms-transform: rotate(3deg);
 -  -webkit-transition: -webkit-transform 100ms linear;
 -  -moz-transition: -moz-transform 100ms linear;
 -  transition: transform 100ms linear;
 -}
 -.openerp .oe_kanban_view .oe_kanban_left {
 -  float: left;
 -}
 -.openerp .oe_kanban_view .oe_kanban_right {
 -  float: right;
 -}
 -.openerp .oe_kanban_view .oe_kanban_clear {
 -  clear: both;
 -}
 -.openerp .oe_kanban_view .oe_kanban_content {
 -  word-wrap: break-word;
 -}
 -.openerp .oe_kanban_view .oe_kanban_content .oe_star_on, .openerp .oe_kanban_view .oe_kanban_content .oe_star_off {
 -  color: #cccccc;
 -  text-shadow: 0 0 2px black;
 -  vertical-align: top;
 -  position: relative;
 -  top: -5px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_content .oe_star_on:hover, .openerp .oe_kanban_view .oe_kanban_content .oe_star_off:hover {
 -  text-decoration: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_content .oe_star_on {
 -  color: gold;
 -}
 -.openerp .oe_kanban_view .oe_kanban_content div:first-child {
 -  margin-right: 16px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_button_new {
 -  color: white;
 -  background: #dc5f59;
 -}
 -.openerp .oe_kanban_view .oe_kanban_groups {
 -  height: inherit;
 -}
 -.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_groups {
 -  width: 100%;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped_by_m2o .oe_kanban_group_title {
 -  cursor: move;
 -}
 -.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban {
 -  float: right;
 -}
 -.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban > span {
 -  visibility: hidden;
 -}
 -.openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban > span {
 -  visibility: visible;
 -}
 -.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_menu {
 -  font-weight: normal;
 -  font-size: 13px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_title {
 -  position: relative;
 -  font-size: 16px;
 -  font-weight: bold;
 -  color: #333333;
 -  text-shadow: 0 1px 0 white;
 -  margin-right: 30px;
 -  width: 200px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_title .oe_kanban_group_title_text {
 -  margin-right: 4px;
 -  white-space: nowrap;
 -  overflow: hidden;
 -  text-overflow: ellipsis;
 -}
 -.openerp .oe_kanban_view .oe_fold_column .oe_kanban_group_length {
 -  position: absolute;
 -  top: -1px;
 -  right: -14px;
 -  float: right;
 -  display: block;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column, .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_group_header {
 -  width: 185px;
 -  min-width: 185px;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column.oe_kanban_group_folded, .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_group_header.oe_kanban_group_folded {
 -  width: auto;
 -  min-width: 30px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_group_header {
 -  vertical-align: top;
 -  padding: 5px 5px 5px 4px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_column ul, .openerp .oe_kanban_view .oe_kanban_column li, .openerp .oe_kanban_view .oe_kanban_group_header ul, .openerp .oe_kanban_view .oe_kanban_group_header li {
 -  margin: 0;
 -  padding: 0;
 -  list-style-type: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_header.oe_kanban_no_group {
 -  padding: 0px;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_group_header {
 -  background: #f0eeee;
 -  border-left: 1px solid #f0f8f8;
 -  border-right: 1px solid #b9b9b9;
 -}
 -.openerp .oe_kanban_view .oe_form .oe_kanban_column {
 -  padding: 0px;
 -  background: white;
 -}
 -.openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_column_cards {
 -  height: 100%;
 -}
 -.openerp .oe_kanban_view .oe_kanban_aggregates {
 -  padding: 0;
 -  margin: 0px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title, .openerp .oe_kanban_view .oe_kanban_group_folded.oe_kanban_column *, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_aggregates, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_add {
 -  display: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_length {
 -  display: block;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_folded .oe_dropdown_kanban {
 -  left: -5px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_title_undefined {
 -  color: #666666;
 -}
 -.openerp .oe_kanban_view .oe_kanban_group_title_vertical {
 -  writing-mode: tb-rl;
 -  -webkit-transform: rotate(90deg);
 -  -moz-transform: rotate(90deg);
 -  -o-transform: rotate(90deg);
 -  -ms-transform: rotate(90deg);
 -  transform: rotate(90deg);
 -  width: 30px;
 -  font-size: 24px;
 -  white-space: nowrap;
 -  display: none;
 -  position: relative;
 -  opacity: 0.75;
 -  top: 26px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
 -  margin-left: 4px;
 -  cursor: pointer;
 -  position: relative;
 -}
 -.openerp .oe_kanban_view .oe_kanban_add {
 -  top: -8px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
 -  top: -2px;
 -  height: 14px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_dropdown_toggle {
 -  cursor: pointer;
 -  display: inline-block;
 -}
 -.openerp .oe_kanban_view .oe_kanban_add {
 -  float: right;
 -}
 -.openerp .oe_kanban_view .oe_kanban_quick_create_buttons {
 -  margin: 4px 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_no_group .oe_kanban_quick_create {
 -  width: 185px;
 -  padding: 10px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_quick_create input {
 -  -webkit-box-sizing: border-box;
 -  -moz-box-sizing: border-box;
 -  box-sizing: border-box;
 -  outline: none;
 -  border: 1px solid transparent;
 -  display: block;
 -  margin-bottom: 8px;
 -  font-size: 13px;
 -  width: 100%;
 -  -moz-box-shadow: none;
 -  -webkit-box-shadow: none;
 -  -box-shadow: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_quick_create input:focus {
 -  border: 1px solid #a6a6fe;
 -  -moz-box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset;
 -  -webkit-box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset;
 -  -box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset;
 -}
 -.openerp .oe_kanban_view .oe_kanban_vignette {
 -  padding: 8px;
 -  min-height: 100px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_image {
 -  display: inline-block;
 -  vertical-align: top;
 -  width: 64px;
 -  height: 64px;
 -  text-align: center;
 -  overflow: hidden;
 -  -moz-border-radius: 3px;
 -  -webkit-border-radius: 3px;
 -  border-radius: 3px;
 -  -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
 -  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
 -  -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
 -}
 -.openerp .oe_kanban_view .oe_kanban_details {
 -  display: inline-block;
 -  vertical-align: top;
 -  width: 240px;
 -  font-size: 13px;
 -  padding: 0 5px;
 -  color: #4c4c4c;
 -}
 -.openerp .oe_kanban_view .oe_kanban_details h4 {
 -  margin: 0 0 4px 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_details .oe_tag {
 -  display: inline-block;
 -  margin: 0 2px 2px 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_record {
 -  position: relative;
 -  display: block;
 -  min-height: 20px;
 -  margin: 0;
 -  -moz-border-radius: 4px;
 -  -webkit-border-radius: 4px;
 -  border-radius: 4px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_record:last-child {
 -  margin-bottom: 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_record .oe_kanban_title {
 -  font-weight: bold;
 -  margin: 2px 4px;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_record {
 -  margin-bottom: 4px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_avatar_smallbox {
 -  height: 40px;
 -  width: 40px;
 -  border: 1px solid;
 -  border-color: #e5e5e5 #dbdbdb #d2d2d2;
 -  -moz-border-radius: 3px;
 -  -webkit-border-radius: 3px;
 -  border-radius: 3px;
 -  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 -  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 -  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 -}
 -.openerp .oe_kanban_view .oe_kanban_box {
 -  background: white;
 -  border: 2px solid #cccccc;
 -  border-radius: 4px;
 -  -moz-border-radius: 4px;
 -  -webkit-border-radius: 4px;
 -  margin-bottom: 5px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_box_header {
 -  border-bottom: 1px solid #cccccc;
 -}
 -.openerp .oe_kanban_view .oe_kanban_title {
 -  font-size: 95%;
 -  font-weight: bold;
 -  padding: 0 4px 0 4px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_small {
 -  font-size: 80%;
 -  font-weight: normal;
 -}
 -.openerp .oe_kanban_view .oe_kanban_show_more {
 -  clear: both;
 -  text-align: center;
 -}
 -.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_show_more .oe_button {
 -  width: 100%;
 -}
 -.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_column .oe_kanban_record {
 -  display: inline-block;
 -  padding: 2px;
 -  vertical-align: top;
 -  box-sizing: border-box;
 -  -moz-box-sizing: border-box;
 -  -webkit-box-sizing: border-box;
 -}
 -.openerp .oe_kanban_view .oe_kanban_action_button {
 -  height: 22px;
 -  margin: 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_action_a {
 -  text-decoration: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_action_a:hover {
 -  text-decoration: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_table {
 -  width: 100%;
 -  border: none;
 -  border-collapse: collapse;
 -  margin: 0;
 -  padding: 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_table tr td {
 -  padding: 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_table tr td.oe_kanban_title {
 -  padding: 2px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_box_content {
 -  padding: 4px;
 -  font-size: 90%;
 -}
 -.openerp .oe_kanban_view .oe_kanban_button {
 -  border: 1px solid #8ec1da;
 -  background-color: #ddeef6;
 -  border-radius: 3px;
 -  -moz-border-radius: 3px;
 -  -webkit-border-radius: 3px;
 -  color: black;
 -  text-shadow: 0 1px white;
 -  padding: 0 4px;
 -  font-size: 85%;
 -  margin: 1px;
 -}
 -.openerp .oe_kanban_view a.oe_kanban_button:hover, .openerp .oe_kanban_view .openerp button.oe_kanban_button:hover {
 -  background-color: #eeddf6;
 -}
 -.openerp .oe_kanban_view .oe_kanban_buttons_set {
 -  border-top: 1px dotted;
 -  white-space: nowrap;
 -  padding-top: 2px;
 -  position: relative;
 -  clear: both;
 -}
 -.openerp .oe_kanban_view .oe_kanban_buttons_set a {
 -  padding: 2px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_box_show_onclick {
 -  display: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_draghandle {
 -  cursor: move;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_border {
 -  border-color: #cccccc;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_border {
 -  border-color: #cccccc;
 -}
 -.openerp .oe_kanban_view .oe_kanban_tooltip ul, .openerp .oe_kanban_view ul.oe_kanban_tooltip {
 -  padding: 0 0 4px 0;
 -  margin: 5px 0 0 15px;
 -  list-style: circle;
 -}
 -.openerp .oe_kanban_view .oe_kanban_highlight {
 -  border-radius: 2px;
 -  -moz-border-radius: 2px;
 -  -webkit-border-radius: 2px;
 -  padding: 1px 5px;
 -  margin: 1px 4px;
 -  white-space: nowrap;
 -  display: inline-block;
 -  line-height: 1em;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_kanban_quick_create {
 -  margin-bottom: 4px;
 -  position: relative;
 -  display: block;
 -  background: white;
 -  border: 1px solid rgba(0, 0, 0, 0.16);
 -  border-bottom-color: rgba(0, 0, 0, 0.3);
 -  padding: 5px;
 -  display: block;
 -  -webkit-transition: -webkit-transform, -webkit-box-shadow, border 200ms linear;
 -  -moz-border-radius: 4px;
 -  -webkit-border-radius: 4px;
 -  border-radius: 4px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card:not(.ui-sortable-helper):hover, .openerp .oe_kanban_view .oe_kanban_quick_create:not(.ui-sortable-helper):hover {
 -  border: 1px solid #7c7bad;
 -  -moz-box-shadow: 0 0 4px #7c7bad;
 -  -webkit-box-shadow: 0 0 4px #7c7bad;
 -  -box-shadow: 0 0 4px #7c7bad;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card:not(.ui-sortable-helper):hover .oe_dropdown_kanban > span, .openerp .oe_kanban_view .oe_kanban_quick_create:not(.ui-sortable-helper):hover .oe_dropdown_kanban > span {
 -  visibility: visible;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card h3, .openerp .oe_kanban_view .oe_kanban_quick_create h3 {
 -  margin: 0 16px 0 0;
 -  color: #4c4c4c;
 -  text-decoration: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card h3:hover, .openerp .oe_kanban_view .oe_kanban_quick_create h3:hover {
 -  text-decoration: none;
 -}
 -.openerp .oe_kanban_view .oe_kanban_card .oe_dropdown_kanban .oe_kanban_project_times li, .openerp .oe_kanban_view .oe_kanban_quick_create .oe_dropdown_kanban .oe_kanban_project_times li {
 -  float: left;
 -}
 -.openerp .oe_kanban_view .oe_kanban_star {
 -  float: left;
 -  position: inline-block;
 -  margin: 0 4px 0 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_avatar {
 -  -moz-border-radius: 3px;
 -  -webkit-border-radius: 3px;
 -  border-radius: 3px;
 -  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 -  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 -  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 -}
 -.openerp .oe_kanban_view .oe_kanban_footer_left {
 -  margin-top: 2px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_footer_left > span {
 -  margin-top: 2px;
 -  display: inline-block;
 -  background: #e6e6e6;
 -  border: 1px solid #b9b9b9;
 -  color: #666666;
 -  padding: 0 2px;
 -  line-height: 16px;
 -  -moz-border-radius: 3px;
 -  -webkit-border-radius: 3px;
 -  border-radius: 3px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_footer_left > span .oe_e {
 -  line-height: 12px;
 -  font-size: 22px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_footer_left .oe_tags {
 -  margin-right: 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_footer_left .oe_tags .oe_tag {
 -  display: inline-block;
 -  padding: 0 2px;
 -  line-height: 14px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_footer_left .oe_kanban_mail_new {
 -  line-height: 18px;
 -  background-color: #8a89ba;
 -  color: white;
 -  font-weight: bold;
 -  position: relative;
 -  top: -1px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_bottom_right {
 -  float: right;
 -  position: relative;
 -  top: 2px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_status {
 -  position: relative;
 -  top: 4px;
 -  display: inline-block;
 -  height: 12px;
 -  width: 12px;
 -  -moz-border-radius: 6px;
 -  -webkit-border-radius: 6px;
 -  border-radius: 6px;
 -  background-position: center center;
 -  background-image: -webkit-radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 -  background-image: -moz-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 -  background-image: -ms-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 -  background-image: radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 -}
 -.openerp .oe_kanban_view .oe_kanban_status_green {
 -  background: green;
 -  background-position: center center;
 -  background-image: -webkit-radial-gradient(circle, #55dd55 0%, #44aa44 40%, #339933 100%);
 -  background-image: -moz-radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
 -  background-image: -ms-radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
 -  background-image: radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
 -}
 -.openerp .oe_kanban_view .oe_kanban_status_red {
 -  background: red;
 -  background-position: center center;
 -  background-image: -webkit-radial-gradient(circle, #ee7777 0%, #cc3333 40%, #bb0808 100%);
 -  background-image: -moz-radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
 -  background-image: -ms-radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
 -  background-image: radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
 -}
 -.openerp .oe_kanban_view .oe_kanban_text_red {
 -  color: #a61300;
 -  font-weight: bold;
 -  -moz-border-radius: 4px;
 -  -webkit-border-radius: 4px;
 -  border-radius: 4px;
 -}
 -.openerp .oe_kanban_view .oe_kanban_ellipsis {
 -  overflow: hidden;
 -  text-overflow: ellipsis;
 -  white-space: nowrap;
 -}
 -.openerp .oe_kanban_view .oe_dropdown_kanban {
 -  float: right;
 -  cursor: pointer;
 -  margin-top: -6px;
 -}
 -.openerp .oe_kanban_view .oe_dropdown_kanban:hover {
 -  text-decoration: none;
 -}
 -.openerp .oe_kanban_view .oe_dropdown_kanban .oe_dropdown_menu {
 -  left: 0;
 -  top: 28px;
 -  min-width: 160px;
 -  padding: 2px;
 -}
 -.openerp .oe_kanban_view .oe_dropdown_kanban .oe_dropdown_menu > li {
 -  padding: 3px;
 -}
 -.openerp .oe_kanban_view .oe_dropdown_kanban.oe_opened > span {
 -  visibility: visible;
 -}
 -.openerp .oe_kanban_view .oe_dropdown_kanban > span {
 -  visibility: hidden;
 -}
 -.openerp .oe_kanban_view .oe_kanban_colorpicker {
 -  white-space: nowrap;
 -}
 -.openerp .oe_kanban_view .oe_kanban_colorpicker li {
 -  float: left;
 -  margin: 0;
 -  padding: 0;
 -}
 -.openerp .oe_kanban_view .oe_kanban_colorpicker li a {
 -  display: inline-block;
 -  width: 16px;
 -  height: 16px;
 -  border: 1px solid white;
 -}
 -.openerp .oe_kanban_view .oe_kanban_colorpicker li a:hover {
 -  border: 1px solid gray !important;
 -}
 -.openerp .oe_kanban_view .oe_kanban_colorpicker li:first-child a {
 -  border: 1px solid #cccccc;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_0 {
 -  background-color: white;
 -  color: #5a5a5a;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_1 {
 -  background-color: #cccccc;
 -  color: #424242;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_2 {
 -  background-color: #ffc7c7;
 -  color: #7a3737;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_3 {
 -  background-color: #fff1c7;
 -  color: #756832;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_4 {
 -  background-color: #e3ffc7;
 -  color: #5d6937;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_5 {
 -  background-color: #c7ffd5;
 -  color: #1a7759;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_6 {
 -  background-color: #c7ffff;
 -  color: #1a5d83;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_7 {
 -  background-color: #c7d5ff;
 -  color: #3b3e75;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_8 {
 -  background-color: #e3c7ff;
 -  color: #4c3668;
 -}
 -.openerp .oe_kanban_view .oe_kanban_color_9 {
 -  background-color: #ffc7f1;
 -  color: #6d2c70;
 -}
 +  height: inherit; }
 +  .openerp .oe_kanban_view .oe_view_nocontent {
 +    position: relative;
 +    z-index: 1;
 +    max-width: none;
 +    height: 100%; }
 +    .openerp .oe_kanban_view .oe_view_nocontent .oe_view_nocontent_content {
 +      margin-left: 90px;
 +      margin-top: 5px;
 +      max-width: 700px; }
 +    .openerp .oe_kanban_view .oe_view_nocontent .oe_view_nocontent_bg {
 +      background: #eeeeee;
 +      opacity: 0.7;
 +      position: absolute;
 +      top: 0;
 +      bottom: 0;
 +      left: 0;
 +      right: 0;
 +      z-index: -1; }
 +  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_dummy_cell {
 +    background: url(/web/static/src/img/form_sheetbg.png);
 +    width: 100%; }
 +  .openerp .oe_kanban_view .oe_kanban_group_length {
 +    text-align: center;
 +    display: none; }
 +    .openerp .oe_kanban_view .oe_kanban_group_length .oe_tag {
 +      position: relative;
 +      top: 8px;
 +      font-weight: bold; }
 +  .openerp .oe_kanban_view .oe_kanban_header:hover .oe_kanban_group_length {
 +    display: none; }
 +  .openerp .oe_kanban_view .ui-sortable-placeholder {
 +    border: 1px solid rgba(0, 0, 0, 0.1);
 +    visibility: visible !important; }
 +  .openerp .oe_kanban_view .ui-sortable-helper {
 +    -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
 +    -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
 +    -box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
 +    -moz-transform: rotate(3deg);
 +    -webkit-transform: rotate(3deg);
 +    -o-transform: rotate(3deg);
 +    -ms-transform: rotate(3deg);
 +    -webkit-transition: -webkit-transform 100ms linear;
 +    -moz-transition: -moz-transform 100ms linear;
 +    transition: transform 100ms linear; }
 +  .openerp .oe_kanban_view .oe_kanban_left {
 +    float: left; }
 +  .openerp .oe_kanban_view .oe_kanban_right {
 +    float: right; }
 +  .openerp .oe_kanban_view .oe_kanban_clear {
 +    clear: both; }
 +  .openerp .oe_kanban_view .oe_kanban_content {
 +    word-wrap: break-word; }
 +    .openerp .oe_kanban_view .oe_kanban_content .oe_star_on, .openerp .oe_kanban_view .oe_kanban_content .oe_star_off {
 +      color: #cccccc;
 +      text-shadow: 0 0 2px black;
 +      vertical-align: top;
 +      position: relative;
 +      top: -5px; }
 +      .openerp .oe_kanban_view .oe_kanban_content .oe_star_on:hover, .openerp .oe_kanban_view .oe_kanban_content .oe_star_off:hover {
 +        text-decoration: none; }
 +    .openerp .oe_kanban_view .oe_kanban_content .oe_star_on {
 +      color: gold; }
 +    .openerp .oe_kanban_view .oe_kanban_content div:first-child {
 +      margin-right: 16px; }
 +  .openerp .oe_kanban_view .oe_kanban_button_new {
 +    color: white;
 +    background: #dc5f59; }
 +  .openerp .oe_kanban_view .oe_kanban_groups {
 +    height: inherit; }
 +  .openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_groups {
 +    width: 100%; }
 +  .openerp .oe_kanban_view.oe_kanban_grouped_by_m2o .oe_kanban_group_title {
 +    cursor: move; }
 +  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban {
 +    float: right; }
 +  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban > span {
 +    visibility: hidden; }
 +  .openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban > span {
 +    visibility: visible; }
 +  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_menu {
 +    font-weight: normal;
 +    font-size: 13px; }
 +  .openerp .oe_kanban_view .oe_kanban_group_title {
 +    position: relative;
 +    font-size: 16px;
 +    font-weight: bold;
 +    color: #333333;
 +    text-shadow: 0 1px 0 white;
 +    margin-right: 30px;
 +    width: 200px; }
 +    .openerp .oe_kanban_view .oe_kanban_group_title .oe_kanban_group_title_text {
 +      margin-right: 4px;
 +      white-space: nowrap;
 +      overflow: hidden;
 +      text-overflow: ellipsis; }
 +  .openerp .oe_kanban_view .oe_fold_column .oe_kanban_group_length {
 +    position: absolute;
 +    top: -1px;
 +    right: -14px;
 +    float: right;
 +    display: block; }
 +  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column, .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_group_header {
 +    width: 185px;
 +    min-width: 185px; }
 +    .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column.oe_kanban_group_folded, .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_group_header.oe_kanban_group_folded {
 +      width: auto;
 +      min-width: 30px; }
 +  .openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_group_header {
 +    vertical-align: top;
 +    padding: 5px 5px 5px 4px; }
 +    .openerp .oe_kanban_view .oe_kanban_column ul, .openerp .oe_kanban_view .oe_kanban_column li, .openerp .oe_kanban_view .oe_kanban_group_header ul, .openerp .oe_kanban_view .oe_kanban_group_header li {
 +      margin: 0;
 +      padding: 0;
 +      list-style-type: none; }
 +  .openerp .oe_kanban_view .oe_kanban_group_header.oe_kanban_no_group {
 +    padding: 0px; }
 +  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_group_header {
 +    background: #f0eeee;
 +    border-left: 1px solid #f0f8f8;
 +    border-right: 1px solid #b9b9b9; }
 +  .openerp .oe_kanban_view .oe_form .oe_kanban_column {
 +    padding: 0px;
 +    background: white; }
 +  .openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_column_cards {
 +    height: 100%; }
 +  .openerp .oe_kanban_view .oe_kanban_aggregates {
 +    padding: 0;
 +    margin: 0px; }
 +  .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title, .openerp .oe_kanban_view .oe_kanban_group_folded.oe_kanban_column *, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_aggregates, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_add {
 +    display: none; }
 +  .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_length {
 +    display: block; }
 +  .openerp .oe_kanban_view .oe_kanban_group_folded .oe_dropdown_kanban {
 +    left: -5px; }
 +  .openerp .oe_kanban_view .oe_kanban_group_title_undefined {
 +    color: #666666; }
 +  .openerp .oe_kanban_view .oe_kanban_group_title_vertical {
 +    writing-mode: tb-rl;
 +    -webkit-transform: rotate(90deg);
 +    -moz-transform: rotate(90deg);
 +    -o-transform: rotate(90deg);
 +    -ms-transform: rotate(90deg);
 +    transform: rotate(90deg);
 +    width: 30px;
 +    font-size: 24px;
 +    white-space: nowrap;
 +    display: none;
 +    position: relative;
 +    opacity: 0.75;
 +    top: 26px; }
 +  .openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
 +    margin-left: 4px;
 +    cursor: pointer;
 +    position: relative; }
 +  .openerp .oe_kanban_view .oe_kanban_add {
 +    top: -8px;
 +    z-index: 2; }
 +  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
 +    top: -2px;
 +    height: 14px; }
 +  .openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_dropdown_toggle {
 +    cursor: pointer;
 +    display: inline-block; }
 +  .openerp .oe_kanban_view .oe_kanban_add {
 +    float: right; }
 +  .openerp .oe_kanban_view .oe_kanban_quick_create_buttons {
 +    margin: 4px 0; }
 +  .openerp .oe_kanban_view .oe_kanban_no_group .oe_kanban_quick_create {
 +    width: 185px;
 +    padding: 10px; }
 +  .openerp .oe_kanban_view .oe_kanban_quick_create {
 +    z-index: 2; }
 +  .openerp .oe_kanban_view .oe_kanban_quick_create input {
 +    -webkit-box-sizing: border-box;
 +    -moz-box-sizing: border-box;
 +    box-sizing: border-box;
 +    outline: none;
 +    border: 1px solid transparent;
 +    display: block;
 +    margin-bottom: 8px;
 +    font-size: 13px;
 +    width: 100%;
 +    -moz-box-shadow: none;
 +    -webkit-box-shadow: none;
 +    -box-shadow: none; }
 +    .openerp .oe_kanban_view .oe_kanban_quick_create input:focus {
 +      border: 1px solid #a6a6fe;
 +      -moz-box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset;
 +      -webkit-box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset;
 +      -box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset; }
 +  .openerp .oe_kanban_view .oe_kanban_vignette {
 +    padding: 8px;
 +    min-height: 100px; }
 +  .openerp .oe_kanban_view .oe_kanban_image {
 +    display: inline-block;
 +    vertical-align: top;
 +    width: 64px;
 +    height: 64px;
 +    text-align: center;
 +    overflow: hidden;
 +    -moz-border-radius: 3px;
 +    -webkit-border-radius: 3px;
 +    border-radius: 3px;
 +    -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
 +    -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
 +    -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); }
 +  .openerp .oe_kanban_view .oe_kanban_details {
 +    display: inline-block;
 +    vertical-align: top;
 +    width: 240px;
 +    font-size: 13px;
 +    padding: 0 5px;
 +    color: #4c4c4c; }
 +    .openerp .oe_kanban_view .oe_kanban_details h4 {
 +      margin: 0 0 4px 0; }
 +    .openerp .oe_kanban_view .oe_kanban_details .oe_tag {
 +      display: inline-block;
 +      margin: 0 2px 2px 0; }
 +  .openerp .oe_kanban_view .oe_kanban_record {
 +    position: relative;
 +    display: block;
 +    min-height: 20px;
 +    margin: 0;
 +    -moz-border-radius: 4px;
 +    -webkit-border-radius: 4px;
 +    border-radius: 4px; }
 +    .openerp .oe_kanban_view .oe_kanban_record:last-child {
 +      margin-bottom: 0; }
 +    .openerp .oe_kanban_view .oe_kanban_record .oe_kanban_title {
 +      font-weight: bold;
 +      margin: 2px 4px; }
 +    .openerp .oe_kanban_view .oe_kanban_record .oe_kanban_alias {
 +      margin: 0px 0 8px 0; }
 +      .openerp .oe_kanban_view .oe_kanban_record .oe_kanban_alias .oe_e {
 +        font-size: 30px;
 +        line-height: 6px;
 +        vertical-align: top;
 +        margin-right: 3px;
 +        color: white;
 +        text-shadow: 0px 0px 2px black;
 +        float: left; }
 +  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_record {
 +    margin-bottom: 4px; }
 +  .openerp .oe_kanban_view .oe_kanban_avatar_smallbox {
 +    height: 40px;
 +    width: 40px;
 +    border: 1px solid;
 +    border-color: #e5e5e5 #dbdbdb #d2d2d2;
 +    -moz-border-radius: 3px;
 +    -webkit-border-radius: 3px;
 +    border-radius: 3px;
 +    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 +    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 +    -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
 +  .openerp .oe_kanban_view .oe_kanban_box {
 +    background: white;
 +    border: 2px solid #cccccc;
 +    border-radius: 4px;
 +    -moz-border-radius: 4px;
 +    -webkit-border-radius: 4px;
 +    margin-bottom: 5px; }
 +  .openerp .oe_kanban_view .oe_kanban_box_header {
 +    border-bottom: 1px solid #cccccc; }
 +  .openerp .oe_kanban_view .oe_kanban_title {
 +    font-size: 95%;
 +    font-weight: bold;
 +    padding: 0 4px 0 4px; }
 +  .openerp .oe_kanban_view .oe_kanban_small {
 +    font-size: 80%;
 +    font-weight: normal; }
 +  .openerp .oe_kanban_view .oe_kanban_show_more {
 +    clear: both;
 +    text-align: center; }
 +  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_show_more .oe_button {
 +    width: 100%; }
 +  .openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_column .oe_kanban_record {
 +    display: inline-block;
 +    padding: 2px;
 +    vertical-align: top;
 +    box-sizing: border-box;
 +    -moz-box-sizing: border-box;
 +    -webkit-box-sizing: border-box; }
 +  .openerp .oe_kanban_view .oe_kanban_action_button {
 +    height: 22px;
 +    margin: 0; }
 +  .openerp .oe_kanban_view .oe_kanban_action_a {
 +    text-decoration: none; }
 +    .openerp .oe_kanban_view .oe_kanban_action_a:hover {
 +      text-decoration: none; }
 +  .openerp .oe_kanban_view .oe_kanban_table {
 +    width: 100%;
 +    border: none;
 +    border-collapse: collapse;
 +    margin: 0;
 +    padding: 0; }
 +  .openerp .oe_kanban_view .oe_kanban_table tr td {
 +    padding: 0; }
 +  .openerp .oe_kanban_view .oe_kanban_table tr td.oe_kanban_title {
 +    padding: 2px; }
 +  .openerp .oe_kanban_view .oe_kanban_box_content {
 +    padding: 4px;
 +    font-size: 90%; }
 +  .openerp .oe_kanban_view .oe_kanban_button {
 +    border: 1px solid #8ec1da;
 +    background-color: #ddeef6;
 +    border-radius: 3px;
 +    -moz-border-radius: 3px;
 +    -webkit-border-radius: 3px;
 +    color: black;
 +    text-shadow: 0 1px white;
 +    padding: 0 4px;
 +    font-size: 85%;
 +    margin: 1px; }
 +  .openerp .oe_kanban_view a.oe_kanban_button:hover, .openerp .oe_kanban_view .openerp button.oe_kanban_button:hover {
 +    background-color: #eeddf6; }
 +  .openerp .oe_kanban_view .oe_kanban_buttons_set {
 +    border-top: 1px dotted;
 +    white-space: nowrap;
 +    padding-top: 2px;
 +    position: relative;
 +    clear: both; }
 +    .openerp .oe_kanban_view .oe_kanban_buttons_set a {
 +      padding: 2px; }
 +  .openerp .oe_kanban_view .oe_kanban_box_show_onclick {
 +    display: none; }
 +  .openerp .oe_kanban_view .oe_kanban_draghandle {
 +    cursor: move; }
 +  .openerp .oe_kanban_view .oe_kanban_color_border {
 +    border-color: #cccccc; }
 +  .openerp .oe_kanban_view .oe_kanban_color_border {
 +    border-color: #cccccc; }
 +  .openerp .oe_kanban_view .oe_kanban_tooltip ul, .openerp .oe_kanban_view ul.oe_kanban_tooltip {
 +    padding: 0 0 4px 0;
 +    margin: 5px 0 0 15px;
 +    list-style: circle; }
 +  .openerp .oe_kanban_view .oe_kanban_highlight {
 +    border-radius: 2px;
 +    -moz-border-radius: 2px;
 +    -webkit-border-radius: 2px;
 +    padding: 1px 5px;
 +    margin: 1px 4px;
 +    white-space: nowrap;
 +    display: inline-block;
 +    line-height: 1em; }
 +  .openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_kanban_quick_create {
 +    margin-bottom: 4px;
 +    position: relative;
 +    display: block;
 +    background: white;
 +    border: 1px solid rgba(0, 0, 0, 0.16);
 +    border-bottom-color: rgba(0, 0, 0, 0.3);
 +    padding: 5px;
 +    display: block;
 +    -webkit-transition: -webkit-transform, -webkit-box-shadow, border 200ms linear;
 +    -moz-border-radius: 4px;
 +    -webkit-border-radius: 4px;
 +    border-radius: 4px; }
 +    .openerp .oe_kanban_view .oe_kanban_card:not(.ui-sortable-helper):hover, .openerp .oe_kanban_view .oe_kanban_quick_create:not(.ui-sortable-helper):hover {
 +      border: 1px solid #7c7bad;
 +      -moz-box-shadow: 0 0 4px #7c7bad;
 +      -webkit-box-shadow: 0 0 4px #7c7bad;
 +      -box-shadow: 0 0 4px #7c7bad; }
 +      .openerp .oe_kanban_view .oe_kanban_card:not(.ui-sortable-helper):hover .oe_dropdown_kanban > span, .openerp .oe_kanban_view .oe_kanban_quick_create:not(.ui-sortable-helper):hover .oe_dropdown_kanban > span {
 +        visibility: visible; }
 +    .openerp .oe_kanban_view .oe_kanban_card h3, .openerp .oe_kanban_view .oe_kanban_quick_create h3 {
 +      margin: 0 16px 0 0;
 +      color: #4c4c4c;
 +      text-decoration: none; }
 +    .openerp .oe_kanban_view .oe_kanban_card h3:hover, .openerp .oe_kanban_view .oe_kanban_quick_create h3:hover {
 +      text-decoration: none; }
 +    .openerp .oe_kanban_view .oe_kanban_card .oe_dropdown_kanban .oe_kanban_project_times li, .openerp .oe_kanban_view .oe_kanban_quick_create .oe_dropdown_kanban .oe_kanban_project_times li {
 +      float: left; }
 +  .openerp .oe_kanban_view .oe_kanban_star {
 +    float: left;
 +    position: inline-block;
 +    margin: 0 4px 0 0; }
 +  .openerp .oe_kanban_view .oe_kanban_avatar {
 +    -moz-border-radius: 3px;
 +    -webkit-border-radius: 3px;
 +    border-radius: 3px;
 +    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 +    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
 +    -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
 +  .openerp .oe_kanban_view .oe_kanban_footer_left {
 +    margin-top: 2px; }
 +    .openerp .oe_kanban_view .oe_kanban_footer_left > span {
 +      margin-top: 2px;
 +      display: inline-block;
 +      background: #e6e6e6;
 +      border: 1px solid #b9b9b9;
 +      color: #666666;
 +      padding: 0 2px;
 +      line-height: 16px;
 +      -moz-border-radius: 3px;
 +      -webkit-border-radius: 3px;
 +      border-radius: 3px; }
 +      .openerp .oe_kanban_view .oe_kanban_footer_left > span .oe_e {
 +        line-height: 12px;
 +        font-size: 22px; }
 +    .openerp .oe_kanban_view .oe_kanban_footer_left .oe_tags {
 +      margin-right: 0; }
 +      .openerp .oe_kanban_view .oe_kanban_footer_left .oe_tags .oe_tag {
 +        display: inline-block;
 +        padding: 0 2px;
 +        line-height: 14px; }
 +    .openerp .oe_kanban_view .oe_kanban_footer_left .oe_kanban_mail_new {
 +      line-height: 18px;
 +      background-color: #8a89ba;
 +      color: white;
 +      font-weight: bold;
 +      position: relative;
 +      top: -1px; }
 +  .openerp .oe_kanban_view .oe_kanban_bottom_right {
 +    float: right;
 +    position: relative;
 +    top: 2px; }
 +  .openerp .oe_kanban_view .oe_kanban_status {
 +    position: relative;
 +    top: 4px;
 +    display: inline-block;
 +    height: 12px;
 +    width: 12px;
 +    -moz-border-radius: 6px;
 +    -webkit-border-radius: 6px;
 +    border-radius: 6px;
 +    background-position: center center;
 +    background-image: -webkit-radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 +    background-image: -moz-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 +    background-image: -ms-radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 +    background-image: radial-gradient(#eeeeee 0%, #cccccc 40%, #bbbbbb 100%); }
 +  .openerp .oe_kanban_view .oe_kanban_status_green {
 +    background: green;
 +    background-position: center center;
 +    background-image: -webkit-radial-gradient(circle, #55dd55 0%, #44aa44 40%, #339933 100%);
 +    background-image: -moz-radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
 +    background-image: -ms-radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%);
 +    background-image: radial-gradient(#55dd55 0%, #44aa44 40%, #339933 100%); }
 +  .openerp .oe_kanban_view .oe_kanban_status_red {
 +    background: red;
 +    background-position: center center;
 +    background-image: -webkit-radial-gradient(circle, #ee7777 0%, #cc3333 40%, #bb0808 100%);
 +    background-image: -moz-radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
 +    background-image: -ms-radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%);
 +    background-image: radial-gradient(#ee7777 0%, #cc3333 40%, #bb0808 100%); }
 +  .openerp .oe_kanban_view .oe_kanban_text_red {
 +    color: #a61300;
 +    font-weight: bold;
 +    -moz-border-radius: 4px;
 +    -webkit-border-radius: 4px;
 +    border-radius: 4px; }
 +  .openerp .oe_kanban_view .oe_kanban_ellipsis {
 +    overflow: hidden;
 +    text-overflow: ellipsis;
 +    white-space: nowrap; }
 +  .openerp .oe_kanban_view .oe_dropdown_kanban {
 +    float: right;
 +    cursor: pointer;
 +    margin-top: -6px; }
 +    .openerp .oe_kanban_view .oe_dropdown_kanban:hover {
 +      text-decoration: none; }
 +    .openerp .oe_kanban_view .oe_dropdown_kanban .oe_dropdown_menu {
 +      left: 0;
 +      top: 28px;
 +      min-width: 160px;
 +      padding: 2px; }
 +      .openerp .oe_kanban_view .oe_dropdown_kanban .oe_dropdown_menu > li {
 +        padding: 3px; }
 +  .openerp .oe_kanban_view .oe_dropdown_kanban.oe_opened > span {
 +    visibility: visible; }
 +  .openerp .oe_kanban_view .oe_dropdown_kanban > span {
 +    visibility: hidden; }
 +  .openerp .oe_kanban_view .oe_kanban_colorpicker {
 +    white-space: nowrap; }
 +  .openerp .oe_kanban_view .oe_kanban_colorpicker li {
 +    float: left;
 +    margin: 0;
 +    padding: 0; }
 +    .openerp .oe_kanban_view .oe_kanban_colorpicker li a {
 +      display: inline-block;
 +      width: 16px;
 +      height: 16px;
 +      border: 1px solid white; }
 +    .openerp .oe_kanban_view .oe_kanban_colorpicker li a:hover {
 +      border: 1px solid gray !important; }
 +  .openerp .oe_kanban_view .oe_kanban_colorpicker li:first-child a {
 +    border: 1px solid #cccccc; }
 +  .openerp .oe_kanban_view .oe_kanban_color_0 {
 +    background-color: white;
 +    color: #5a5a5a; }
 +  .openerp .oe_kanban_view .oe_kanban_color_1 {
 +    background-color: #cccccc;
 +    color: #424242; }
 +  .openerp .oe_kanban_view .oe_kanban_color_2 {
 +    background-color: #ffc7c7;
 +    color: #7a3737; }
 +  .openerp .oe_kanban_view .oe_kanban_color_3 {
 +    background-color: #fff1c7;
 +    color: #756832; }
 +  .openerp .oe_kanban_view .oe_kanban_color_4 {
 +    background-color: #e3ffc7;
 +    color: #5d6937; }
 +  .openerp .oe_kanban_view .oe_kanban_color_5 {
 +    background-color: #c7ffd5;
 +    color: #1a7759; }
 +  .openerp .oe_kanban_view .oe_kanban_color_6 {
 +    background-color: #c7ffff;
 +    color: #1a5d83; }
 +  .openerp .oe_kanban_view .oe_kanban_color_7 {
 +    background-color: #c7d5ff;
 +    color: #3b3e75; }
 +  .openerp .oe_kanban_view .oe_kanban_color_8 {
 +    background-color: #e3c7ff;
 +    color: #4c3668; }
 +  .openerp .oe_kanban_view .oe_kanban_color_9 {
 +    background-color: #ffc7f1;
 +    color: #6d2c70; }
  
  .openerp .oe_form .oe_kanban_view .oe_kanban_column, .openerp .oe_form .oe_kanban_view .oe_kanban_group_header {
    padding: 0px;
 -  background: white;
 -}
 +  background: white; }
  
  .openerp .oe_popup_form .oe_kanban_buttons .oe_highlight {
    color: #404040;
 -  background: none;
 -}
 +  background: none; }
  .openerp .oe_popup_form .oe_kanban_buttons button.oe_highlight {
    background-color: #efefef;
    background-image: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#d8d8d8));
    background-image: linear-gradient(to bottom, #efefef, #d8d8d8);
    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
 -  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
 -}
 +  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; }
  .openerp .oe_popup_form .oe_kanban_buttons button.oe_highlight:active {
    background-color: #e3e3e3;
    background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6));
    background-image: linear-gradient(to bottom, #e3e3e3, #f6f6f6);
    -moz-box-shadow: none;
    -webkit-box-shadow: none;
 -  -box-shadow: none;
 -}
 +  -box-shadow: none; }
  .openerp .oe_popup_form .oe_kanban_buttons button.oe_highlight:hover {
    background-color: #f6f6f6;
    background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3));
    background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3);
    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
 -  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
 -}
 +  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; }
  
  .openerp_ie .oe_kanban_view .oe_kanban_group_header .oe_kanban_group_title_vertical {
 -  display: none !important;
 -}
 +  display: none !important; }
  .openerp_ie .oe_kanban_view .oe_kanban_group_header.oe_kanban_group_folded .oe_kanban_group_title_vertical {
 -  display: inline-block !important;
 -}
 +  display: inline-block !important; }
  .openerp_ie .oe_kanban_view .oe_kanban_group_title_vertical {
    -ms-writing-mode: lr-tb !important;
--  background: #f0eeee;
-   top: -5px !important; }
 -}
++  background: #f0eeee;}
++
  .openerp_ie .oe_kanban_view.oe_kanban_grouped .oe_kanban_group_header {
 -  height: 1%;
 -}
 +  height: 1%; }
  
  @media print {
    .openerp .oe_kanban_groups button {
 -    visibility: hidden;
 -  }
 +    visibility: hidden; }
    .openerp .oe_kanban_groups a[data-type=object], .openerp .oe_kanban_groups a[data-type=delete] {
 -    visibility: hidden;
 -  }
 +    visibility: hidden; }
    .openerp .oe_kanban_view .oe_kanban_group_title {
 -    text-shadow: none !important;
 -  }
 -}
 +    text-shadow: none !important; } }
      //background: url(data:image/pngbase64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAKElEQVQIHWP8DwTv379nAAFBQUEGhnfv3oHEwADEZgJLIRGMIClkLQCr3x2Htp/lLwAAAABJRU5ErkJggg==)
      background: white
      height: inherit
 -    &.oe_kanban_grouped .oe_kanban_dummy_cell
 -        background: url(/web/static/src/img/form_sheetbg.png)
 -        width: 100%
 +    .oe_view_nocontent
 +        position: relative
 +        z-index: 1
 +        max-width: none
 +        height: 100%
 +        .oe_view_nocontent_content
 +            margin-left: 90px
 +            margin-top: 5px
 +            max-width: 700px
 +        .oe_view_nocontent_bg
 +            background: #eee
 +            opacity: 0.7
 +            position: absolute
 +            top: 0
 +            bottom: 0
 +            left: 0
 +            right: 0
 +            z-index: -1
 +    &.oe_kanban_grouped
 +        .oe_kanban_dummy_cell
 +            background: url(/web/static/src/img/form_sheetbg.png)
 +            width: 100%
      .oe_kanban_group_length
          text-align: center
          display: none
          position: relative
      .oe_kanban_add
          top: -8px
 +        z-index: 2
      .oe_kanban_header .oe_dropdown_toggle
          top: -2px
          height: 14px
      .oe_kanban_no_group .oe_kanban_quick_create
          width: 185px
          padding: 10px
 +    .oe_kanban_quick_create
 +        z-index: 2
      .oe_kanban_quick_create input
          @include box-sizing(border-box)
          outline: none
          .oe_kanban_title
              font-weight: bold
              margin: 2px 4px
 +        .oe_kanban_alias
 +            margin: 0px 0 8px 0
 +            .oe_e
 +                font-size: 30px
 +                line-height: 6px
 +                vertical-align: top
 +                margin-right: 3px
 +                color: white
 +                text-shadow: 0px 0px 2px rgba(0, 0, 0, 1)
 +                float: left
      &.oe_kanban_grouped
          .oe_kanban_record
              margin-bottom: 4px
      .oe_kanban_group_title_vertical
          -ms-writing-mode: lr-tb !important
          background: rgb(240, 238, 238)
-         top: -5px !important
      &.oe_kanban_grouped
          .oe_kanban_group_header
              height: 1%
@@@ -225,6 -225,7 +225,6 @@@ instance.web_kanban.KanbanView = instan
      },
      do_search: function(domain, context, group_by) {
          var self = this;
 -        this.$el.find('.oe_view_nocontent').remove();
          this.search_domain = domain;
          this.search_context = context;
          this.search_group_by = group_by;
              self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o);
              self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o);
              var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined;
-             var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(grouping_fields);
+             if (!_.isEmpty(grouping_fields)) {
+                 // ensure group_by fields are read.
+                 self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields));
+             }
+             var grouping = new instance.web.Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields);
              return self.alive($.when(grouping)).done(function(groups) {
 +                self.remove_no_result();
                  if (groups) {
                      self.do_process_groups(groups);
                  } else {
      },
      do_process_groups: function(groups) {
          var self = this;
 +        this.$el.find('table:first').show();
          this.$el.removeClass('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
          this.add_group_mutex.exec(function() {
              self.do_clear_groups();
                  self.no_result();
                  return false;
              }
 +            self.nb_records = 0;
              var remaining = groups.length - 1,
                  groups_array = [];
              return $.when.apply(null, _.map(groups, function (group, index) {
                      def = dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit });
                  }
                  return def.then(function(records) {
 +                        self.nb_records += records.length;
                          self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
                          groups_array[index] = new instance.web_kanban.KanbanGroup(self, records, group, dataset);
                          if (!remaining--) {
                              return self.do_add_groups(groups_array);
                          }
                  });
 -            }));
 +            })).then(function () {
 +                if(!self.nb_records) {
 +                    self.no_result();
 +                }
 +            });
          });
      },
      do_process_dataset: function() {
          var self = this;
 +        this.$el.find('table:first').show();
          this.$el.removeClass('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
          this.add_group_mutex.exec(function() {
              var def = $.Deferred();
          var $last_td = self.$el.find('.oe_kanban_groups_headers td:last');
          var groups_started = _.map(this.groups, function(group) {
              if (!group.is_started) {
 +                group.on("add_record", self, function () {
 +                    self.remove_no_result();
 +                });
                  return group.insertBefore($last_td);
              }
          });
                  new_group.do_save_sequences();
              }).fail(function(error, evt) {
                  evt.preventDefault();
 -                alert(_t("An error has occured while moving the record to this group: ") + data.fault_code);
 +                alert(_t("An error has occured while moving the record to this group: ") + data.message);
                  self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
              });
          }
          }
      },
      no_result: function() {
 +        var self = this;
          if (this.groups.group_by
              || !this.options.action
 -            || !this.options.action.help) {
 +            || (!this.options.action.help && !this.options.action.get_empty_list_help)) {
              return;
          }
 -        this.$el.find('.oe_view_nocontent').remove();
 -        this.$el.prepend(
 -            $('<div class="oe_view_nocontent">').html(this.options.action.help)
 -        );
 -        var create_nocontent = this.$buttons;
 +        this.$el.find('table:first').css("position", "absolute");
 +        $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertAfter(this.$('table:first'));
          this.$el.find('.oe_view_nocontent').click(function() {
 -            create_nocontent.openerpBounce();
 +            self.$buttons.openerpBounce();
          });
      },
 +    remove_no_result: function() {
 +        this.$el.find('table:first').css("position", false);
 +        this.$el.find('.oe_view_nocontent').remove();
 +    },
  
      /*
      *  postprocessing of fields type many2many
@@@ -589,26 -580,19 +593,26 @@@ instance.web_kanban.KanbanGroup = insta
          });
  
          this.$el.find('.oe_kanban_add').click(function () {
 +            if (self.view.quick) {
 +                self.view.quick.trigger('close');
 +            }
              if (self.quick) {
 -                return self.quick.trigger('close');
 +                return false;
              }
 +            self.view.$el.find('.oe_view_nocontent').hide();
              var ctx = {};
              ctx['default_' + self.view.group_by] = self.value;
              self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, ctx, true)
                  .on('added', self, self.proxy('quick_created'))
                  .on('close', self, function() {
 +                    self.view.$el.find('.oe_view_nocontent').show();
                      this.quick.destroy();
 +                    delete self.view.quick;
                      delete this.quick;
                  });
              self.quick.appendTo($(".oe_kanban_group_list_header", self.$records));
              self.quick.focus();
 +            self.view.quick = self.quick;
          });
          // Add bounce effect on image '+' of kanban header when click on empty space of kanban grouped column.
          this.$records.on('click', '.oe_kanban_show_more', this.do_show_more);
       */
      quick_created: function (record) {
          var id = record, self = this;
 +        self.view.remove_no_result();
 +        self.trigger("add_record");
          this.dataset.read_ids([id], this.view.fields_keys)
              .done(function (records) {
                  self.view.dataset.ids.push(id);
diff --combined openerp/osv/orm.py
@@@ -19,6 -19,8 +19,6 @@@
  #
  ##############################################################################
  
 -#.apidoc title: Object Relational Mapping
 -#.apidoc module-mods: member-order: bysource
  
  """
    Object relational mapping to database (postgresql) module
@@@ -59,6 -61,7 +59,6 @@@ from lxml import etre
  
  import fields
  import openerp
 -import openerp.netsvc as netsvc
  import openerp.tools as tools
  from openerp.tools.config import config
  from openerp.tools.misc import CountingStream
@@@ -422,7 -425,7 +422,7 @@@ class browse_record(object)
                  for field_name, field_column in fields_to_fetch:
                      if field_column._type == 'many2one':
                          if result_line[field_name]:
 -                            obj = self._table.pool.get(field_column._obj)
 +                            obj = self._table.pool[field_column._obj]
                              if isinstance(result_line[field_name], (list, tuple)):
                                  value = result_line[field_name][0]
                              else:
                          else:
                              new_data[field_name] = browse_null()
                      elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
 -                        new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(field_column._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
 +                        new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool[field_column._obj], self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
                      elif field_column._type == 'reference':
                          if result_line[field_name]:
                              if isinstance(result_line[field_name], browse_record):
                                  ref_obj, ref_id = result_line[field_name].split(',')
                                  ref_id = long(ref_id)
                                  if ref_id:
 -                                    obj = self._table.pool.get(ref_obj)
 +                                    obj = self._table.pool[ref_obj]
                                      new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
                                  else:
                                      new_data[field_name] = browse_null()
          try:
              return self[name]
          except KeyError, e:
 -            raise AttributeError(e)
 +            import sys
 +            exc_info = sys.exc_info()
 +            raise AttributeError, "Got %r while trying to get attribute %s on a %s record." % (e, name, self._table._name), exc_info[2]
  
      def __contains__(self, name):
          return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
@@@ -869,10 -870,10 +869,10 @@@ class BaseModel(object)
                  raise TypeError('_name is mandatory in case of multiple inheritance')
  
              for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
 -                parent_model = pool.get(parent_name)
 -                if not parent_model:
 +                if parent_name not in pool:
                      raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
                          'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
 +                parent_model = pool[parent_name]
                  if not getattr(cls, '_original_module', None) and name == parent_model._name:
                      cls._original_module = parent_model._original_module
                  parent_class = parent_model.__class__
                  'ondelete': field['on_delete'],
                  'translate': (field['translate']),
                  'manual': True,
 +                '_prefetch': False,
                  #'select': int(field['select_level'])
              }
  
              return ''
  
          def selection_field(in_field):
 -            col_obj = self.pool.get(in_field.keys()[0])
 +            col_obj = self.pool[in_field.keys()[0]]
              if f[i] in col_obj._columns.keys():
                  return  col_obj._columns[f[i]]
              elif f[i] in col_obj._inherits.keys():
                                  if not data[fpos]:
                                      dt = ''
                                      for rr in r:
 -                                        name_relation = self.pool.get(rr._table_name)._rec_name
 +                                        name_relation = self.pool[rr._table_name]._rec_name
                                          if isinstance(rr[name_relation], browse_record):
                                              rr = rr[name_relation]
 -                                        rr_name = self.pool.get(rr._table_name).name_get(cr, uid, [rr.id], context=context)
 +                                        rr_name = self.pool[rr._table_name].name_get(cr, uid, [rr.id], context=context)
                                          rr_name = rr_name and rr_name[0] and rr_name[0][1] or ''
                                          dt += tools.ustr(rr_name or '') + ','
                                      data[fpos] = dt[:-1]
                      i += 1
                  if i == len(f):
                      if isinstance(r, browse_record):
 -                        r = self.pool.get(r._table_name).name_get(cr, uid, [r.id], context=context)
 +                        r = self.pool[r._table_name].name_get(cr, uid, [r.id], context=context)
                          r = r and r[0] and r[0][1] or ''
                      data[fpos] = tools.ustr(r or '')
          return [data] + lines
  
          # get the default values for the inherited fields
          for t in self._inherits.keys():
 -            defaults.update(self.pool.get(t).default_get(cr, uid, fields_list,
 -                context))
 +            defaults.update(self.pool[t].default_get(cr, uid, fields_list, context))
  
          # get the default values defined in the object
          for f in fields_list:
              if field in fields_list:
                  fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2]
                  if fld_def._type == 'many2one':
 -                    obj = self.pool.get(fld_def._obj)
 +                    obj = self.pool[fld_def._obj]
                      if not obj.search(cr, uid, [('id', '=', field_value or False)]):
                          continue
                  if fld_def._type == 'many2many':
 -                    obj = self.pool.get(fld_def._obj)
 +                    obj = self.pool[fld_def._obj]
                      field_value2 = []
                      for i in range(len(field_value or [])):
                          if not obj.search(cr, uid, [('id', '=',
                          field_value2.append(field_value[i])
                      field_value = field_value2
                  if fld_def._type == 'one2many':
 -                    obj = self.pool.get(fld_def._obj)
 +                    obj = self.pool[fld_def._obj]
                      field_value2 = []
                      for i in range(len(field_value or [])):
                          field_value2.append({})
                          for field2 in field_value[i]:
                              if field2 in obj._columns.keys() and obj._columns[field2]._type == 'many2one':
 -                                obj2 = self.pool.get(obj._columns[field2]._obj)
 +                                obj2 = self.pool[obj._columns[field2]._obj]
                                  if not obj2.search(cr, uid,
                                          [('id', '=', field_value[i][field2])]):
                                      continue
                              elif field2 in obj._inherit_fields.keys() and obj._inherit_fields[field2][2]._type == 'many2one':
 -                                obj2 = self.pool.get(obj._inherit_fields[field2][2]._obj)
 +                                obj2 = self.pool[obj._inherit_fields[field2][2]._obj]
                                  if not obj2.search(cr, uid,
                                          [('id', '=', field_value[i][field2])]):
                                      continue
          # TODO I believe this loop can be replace by
          # res.extend(self._inherit_fields.key())
          for parent in self._inherits:
 -            res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
 +            res.extend(self.pool[parent].fields_get_keys(cr, user, context))
          return res
  
      def _rec_name_fallback(self, cr, uid, context=None):
                  new_xml = etree.fromstring(encode(xml))
                  ctx = context.copy()
                  ctx['base_model_name'] = self._name
 -                xarch, xfields = self.pool.get(node.get('object')).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
 +                xarch, xfields = self.pool[node.get('object')].__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
                  views['form'] = {
                      'arch': xarch,
                      'fields': xfields
                      column = False
  
                  if column:
 -                    relation = self.pool.get(column._obj)
 +                    relation = self.pool[column._obj] if column._obj else None
  
                      children = False
                      views = {}
          fields = {}
          if node.tag == 'diagram':
              if node.getchildren()[0].tag == 'node':
 -                node_model = self.pool.get(node.getchildren()[0].get('object'))
 +                node_model = self.pool[node.getchildren()[0].get('object')]
                  node_fields = node_model.fields_get(cr, user, None, context)
                  fields.update(node_fields)
                  if not node.get("create") and not node_model.check_access_rights(cr, user, 'create', raise_exception=False):
                      node.set("create", 'false')
              if node.getchildren()[1].tag == 'arrow':
 -                arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, None, context)
 +                arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context)
                  fields.update(arrow_fields)
          else:
              fields = self.fields_get(cr, user, None, context)
                           if view_type == 'tree' or not action[2].get('multi')]
              resprint = [clean(print_) for print_ in resprint
                          if view_type == 'tree' or not print_[2].get('multi')]
 -            #When multi="True" set it will display only in More of the list view 
 +            #When multi="True" set it will display only in More of the list view
              resrelate = [clean(action) for action in resrelate
                           if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')]
  
                          res[lang][f] = self._columns[f].string
          for table in self._inherits:
              cols = intersect(self._inherit_fields.keys(), fields)
 -            res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
 +            res2 = self.pool[table].read_string(cr, uid, id, langs, cols, context)
          for lang in res2:
              if lang in res:
                  res[lang]['code'] = lang
          for table in self._inherits:
              cols = intersect(self._inherit_fields.keys(), vals)
              if cols:
 -                self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
 +                self.pool[table].write_string(cr, uid, id, langs, vals, context)
          return True
  
      def _add_missing_default_values(self, cr, uid, values, context=None):
  
          order = orderby or groupby
          data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=order, context=context)
 -        
 +
          # the IDs of records that have groupby field value = False or '' should be included too
          data_ids += set(alldata.keys()).difference(data_ids)
 -        
 -        if groupby:   
 +
 +        if groupby:
              data = self.read(cr, uid, data_ids, [groupby], context=context)
              # restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
              data_dict = dict((d['id'], d[groupby] ) for d in data)
              result = [{'id': i, groupby: data_dict[i]} for i in data_ids]
          else:
 -            result = [{'id': i} for i in data_ids] 
 +            result = [{'id': i} for i in data_ids]
  
          for d in result:
              if groupby:
          :param query: query object on which the JOIN should be added
          """
          inherits_field = current_model._inherits[parent_model_name]
 -        parent_model = self.pool.get(parent_model_name)
 +        parent_model = self.pool[parent_model_name]
          parent_alias, parent_alias_statement = query.add_join((current_model._table, parent_model._table, inherits_field, 'id', inherits_field), implicit=True)
          return parent_alias
  
          parent_alias = '"%s"' % current_table._table
          while field in current_table._inherit_fields and not field in current_table._columns:
              parent_model_name = current_table._inherit_fields[field][0]
 -            parent_table = self.pool.get(parent_model_name)
 +            parent_table = self.pool[parent_model_name]
              parent_alias = self._inherits_join_add(current_table, parent_model_name, query)
              current_table = parent_table
          return '%s."%s"' % (parent_alias, field)
              return
          _logger.info('Computing parent left and right for table %s...', self._table)
          def browse_rec(root, pos=0):
 -# TODO: set order
 +            # TODO: set order
              where = self._parent_name+'='+str(root)
              if not root:
                  where = self._parent_name+' IS NULL'
                                  _schema.debug(msg, self._table, k, f._type)
  
                              if isinstance(f, fields.many2one):
 -                                dest_model = self.pool.get(f._obj)
 +                                dest_model = self.pool[f._obj]
                                  if dest_model._table != 'ir_actions':
                                      self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
  
  
                              # and add constraints if needed
                              if isinstance(f, fields.many2one):
 -                                if not self.pool.get(f._obj):
 +                                if f._obj not in self.pool:
                                      raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
 -                                dest_model = self.pool.get(f._obj)
 +                                dest_model = self.pool[f._obj]
                                  ref = dest_model._table
                                  # ir_actions is inherited so foreign key doesn't work on it
                                  if ref != 'ir_actions':
  
      def _o2m_raise_on_missing_reference(self, cr, f):
          # TODO this check should be a method on fields.one2many.
 -
 -        other = self.pool.get(f._obj)
 -        if other:
 +        if f._obj in self.pool:
 +            other = self.pool[f._obj]
              # TODO the condition could use fields_get_keys().
              if f._fields_id not in other._columns.keys():
                  if f._fields_id not in other._inherit_fields.keys():
          self._save_relation_table(cr, m2m_tbl)
          cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
          if not cr.dictfetchall():
 -            if not self.pool.get(f._obj):
 +            if f._obj not in self.pool:
                  raise except_orm('Programming Error', 'Many2Many destination model does not exist: `%s`' % (f._obj,))
 -            dest_model = self.pool.get(f._obj)
 +            dest_model = self.pool[f._obj]
              ref = dest_model._table
              cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s"))' % (m2m_tbl, col1, col2, col1, col2))
              # create foreign key references with ondelete=cascade, unless the targets are SQL views
          """
          res = {}
          for table in self._inherits:
 -            other = self.pool.get(table)
 +            other = self.pool[table]
              for col in other._columns.keys():
                  res[col] = (table, self._inherits[table], other._columns[col], table)
              for col in other._inherit_fields.keys():
  
          translation_obj = self.pool.get('ir.translation')
          for parent in self._inherits:
 -            res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
 +            res.update(self.pool[parent].fields_get(cr, user, allfields, context))
  
          for f, field in self._columns.iteritems():
              if (allfields and f not in allfields) or \
  
          return res
  
 +    def get_empty_list_help(self, cr, user, help, context=None):
 +        """ Generic method giving the help message displayed when having
 +            no result to display in a list or kanban view. By default it returns
 +            the help given in parameter that is generally the help message
 +            defined in the action.
 +        """
 +        return help
 +
      def check_field_access_rights(self, cr, user, operation, fields, context=None):
          """
          Check the user access rights on the given fields. This raises Access
  
          """
  
 -        if not context:
 -            context = {}
          self.check_access_rights(cr, user, 'read')
          fields = self.check_field_access_rights(cr, user, 'read', fields)
          if isinstance(ids, (int, long)):
          select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
          result = self._read_flat(cr, user, select, fields, context, load)
  
 -        for r in result:
 -            for key, v in r.items():
 -                if v is None:
 -                    r[key] = False
 -
 -        if isinstance(ids, (int, long, dict)):
 +        if isinstance(ids, (int, long)):
              return result and result[0] or False
          return result
  
              cols = [x for x in intersect(self._inherit_fields.keys(), fields_to_read) if x not in self._columns.keys()]
              if not cols:
                  continue
 -            res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load)
 +            res2 = self.pool[table].read(cr, user, [x[col] for x in res], cols, context, load)
  
              res3 = {}
              for r in res2:
                  if field in self._columns:
                      fobj = self._columns[field]
  
 -                if not fobj:
 -                    continue
 -                groups = fobj.read
 -                if groups:
 -                    edit = False
 -                    for group in groups:
 -                        module = group.split(".")[0]
 -                        grp = group.split(".")[1]
 -                        cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s",  \
 -                                   (grp, module, 'res.groups', user))
 -                        readonly = cr.fetchall()
 -                        if readonly[0][0] >= 1:
 -                            edit = True
 -                            break
 -                        elif readonly[0][0] == 0:
 -                            edit = False
 -                        else:
 -                            edit = False
 -
 -                    if not edit:
 -                        if type(vals[field]) == type([]):
 -                            vals[field] = []
 -                        elif type(vals[field]) == type(0.0):
 -                            vals[field] = 0
 -                        elif type(vals[field]) == type(''):
 -                            vals[field] = '=No Permission='
 -                        else:
 -                            vals[field] = False
 +                if fobj:
 +                    groups = fobj.read
 +                    if groups:
 +                        edit = False
 +                        for group in groups:
 +                            module = group.split(".")[0]
 +                            grp = group.split(".")[1]
 +                            cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s",  \
 +                                       (grp, module, 'res.groups', user))
 +                            readonly = cr.fetchall()
 +                            if readonly[0][0] >= 1:
 +                                edit = True
 +                                break
 +                            elif readonly[0][0] == 0:
 +                                edit = False
 +                            else:
 +                                edit = False
 +
 +                        if not edit:
 +                            if type(vals[field]) == type([]):
 +                                vals[field] = []
 +                            elif type(vals[field]) == type(0.0):
 +                                vals[field] = 0
 +                            elif type(vals[field]) == type(''):
 +                                vals[field] = '=No Permission='
 +                            else:
 +                                vals[field] = False
 +
 +                if vals[field] is None:
 +                    vals[field] = False
 +
          return res
  
      # TODO check READ access
              # Attempt to distinguish record rule restriction vs deleted records,
              # to provide a more specific error message - check if the missinf
              cr.execute('SELECT id FROM ' + self._table + ' WHERE id IN %s', (tuple(missing_ids),))
 -            if cr.rowcount:
 +            forbidden_ids = [x[0] for x in cr.fetchall()]
 +            if forbidden_ids:
                  # the missing ids are (at least partially) hidden by access rules
                  if uid == SUPERUSER_ID:
                      return
 -                _logger.warning('Access Denied by record rules for operation: %s, uid: %s, model: %s', operation, uid, self._name)
 +                _logger.warning('Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s', operation, forbidden_ids, uid, self._name)
                  raise except_orm(_('Access Denied'),
                                   _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
                                      (self._description, operation))
                  if operation in ('read','unlink'):
                      # No need to warn about deleting an already deleted record.
                      # And no error when reading a record that was deleted, to prevent spurious
 -                    # errors for non-transactional search/read sequences coming from clients 
 +                    # errors for non-transactional search/read sequences coming from clients
                      return
                  _logger.warning('Failed operation on deleted record(s): %s, uid: %s, model: %s', operation, uid, self._name)
                  raise except_orm(_('Missing document(s)'),
                      returned_ids = [x['id'] for x in cr.dictfetchall()]
                      self._check_record_rules_result_count(cr, uid, sub_ids, returned_ids, operation, context=context)
  
 -    def _workflow_trigger(self, cr, uid, ids, trigger, context=None):
 -        """Call given workflow trigger as a result of a CRUD operation"""
 -        wf_service = netsvc.LocalService("workflow")
 +    def create_workflow(self, cr, uid, ids, context=None):
 +        """Create a workflow instance for each given record IDs."""
 +        from openerp import workflow
          for res_id in ids:
 -            getattr(wf_service, trigger)(uid, self._name, res_id, cr)
 +            workflow.trg_create(uid, self._name, res_id, cr)
 +        return True
 +
 +    def delete_workflow(self, cr, uid, ids, context=None):
 +        """Delete the workflow instances bound to the given record IDs."""
 +        from openerp import workflow
 +        for res_id in ids:
 +            workflow.trg_delete(uid, self._name, res_id, cr)
 +        return True
 +
 +    def step_workflow(self, cr, uid, ids, context=None):
 +        """Reevaluate the workflow instances of the given record IDs."""
 +        from openerp import workflow
 +        for res_id in ids:
 +            workflow.trg_write(uid, self._name, res_id, cr)
 +        return True
  
 -    def _workflow_signal(self, cr, uid, ids, signal, context=None):
 +    def signal_workflow(self, cr, uid, ids, signal, context=None):
          """Send given workflow signal and return a dict mapping ids to workflow results"""
 -        wf_service = netsvc.LocalService("workflow")
 +        from openerp import workflow
          result = {}
          for res_id in ids:
 -            result[res_id] = wf_service.trg_validate(uid, self._name, res_id, signal, cr)
 +            result[res_id] = workflow.trg_validate(uid, self._name, res_id, signal, cr)
          return result
  
 +    def redirect_workflow(self, cr, uid, old_new_ids, context=None):
 +        """ Rebind the workflow instance bound to the given 'old' record IDs to
 +            the given 'new' IDs. (``old_new_ids`` is a list of pairs ``(old, new)``.
 +        """
 +        from openerp import workflow
 +        for old_id, new_id in old_new_ids:
 +            workflow.trg_redirect(uid, self._name, old_id, new_id, cr)
 +        return True
 +
      def unlink(self, cr, uid, ids, context=None):
          """
          Delete records with given ids
          property_ids = ir_property.search(cr, uid, [('res_id', 'in', ['%s,%s' % (self._name, i) for i in ids])], context=context)
          ir_property.unlink(cr, uid, property_ids, context=context)
  
 -        self._workflow_trigger(cr, uid, ids, 'trg_delete', context=context)
 +        self.delete_workflow(cr, uid, ids, context=context)
  
          self.check_access_rule(cr, uid, ids, 'unlink', context=context)
          pool_model_data = self.pool.get('ir.model.data')
              if ir_value_ids:
                  ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
  
 -        for order, object, store_ids, fields in result_store:
 -            if object != self._name:
 -                obj = self.pool.get(object)
 +        for order, obj_name, store_ids, fields in result_store:
 +            if obj_name != self._name:
 +                obj = self.pool[obj_name]
                  cr.execute('select id from '+obj._table+' where id IN %s', (tuple(store_ids),))
                  rids = map(lambda x: x[0], cr.fetchall())
                  if rids:
                  # TODO: optimize
                  for f in direct:
                      if self._columns[f].translate:
 -                        src_trans = self.pool.get(self._name).read(cr, user, ids, [f])[0][f]
 +                        src_trans = self.pool[self._name].read(cr, user, ids, [f])[0][f]
                          if not src_trans:
                              src_trans = vals[f]
                              # Inserting value to DB
                      v[val] = vals[val]
                      unknown_fields.remove(val)
              if v:
 -                self.pool.get(table).write(cr, user, nids, v, context)
 +                self.pool[table].write(cr, user, nids, v, context)
  
          if unknown_fields:
              _logger.warning(
          result.sort()
  
          done = {}
 -        for order, object, ids_to_update, fields_to_recompute in result:
 -            key = (object, tuple(fields_to_recompute))
 +        for order, model_name, ids_to_update, fields_to_recompute in result:
 +            key = (model_name, tuple(fields_to_recompute))
              done.setdefault(key, {})
              # avoid to do several times the same computation
              todo = []
                  if id not in done[key]:
                      done[key][id] = True
                      todo.append(id)
 -            self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
 +            self.pool[model_name]._store_set_values(cr, user, todo, fields_to_recompute, context)
  
 -        self._workflow_trigger(cr, user, ids, 'trg_write', context=context)
 +        self.step_workflow(cr, user, ids, context=context)
          return True
  
      #
                  del vals[self._inherits[table]]
  
              record_id = tocreate[table].pop('id', None)
 -            
 +
              # When linking/creating parent records, force context without 'no_store_function' key that
 -            # defers stored functions computing, as these won't be computed in batch at the end of create(). 
 +            # defers stored functions computing, as these won't be computed in batch at the end of create().
              parent_context = dict(context)
              parent_context.pop('no_store_function', None)
 -            
 +
              if record_id is None or not record_id:
 -                record_id = self.pool.get(table).create(cr, user, tocreate[table], context=parent_context)
 +                record_id = self.pool[table].create(cr, user, tocreate[table], context=parent_context)
              else:
 -                self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=parent_context)
 +                self.pool[table].write(cr, user, [record_id], tocreate[table], context=parent_context)
  
              upd0 += ',' + self._inherits[table]
              upd1 += ',%s'
                  upd0 = upd0 + ',"' + field + '"'
                  upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
                  upd2.append(self._columns[field]._symbol_set[1](vals[field]))
 -                #for the function fields that receive a value, we set them directly in the database 
 +                #for the function fields that receive a value, we set them directly in the database
                  #(they may be required), but we also need to trigger the _fct_inv()
                  if (hasattr(self._columns[field], '_fnct_inv')) and not isinstance(self._columns[field], fields.related):
                      #TODO: this way to special case the related fields is really creepy but it shouldn't be changed at
              result += self._store_get_values(cr, user, [id_new], vals.keys(), context)
              result.sort()
              done = []
 -            for order, object, ids, fields2 in result:
 -                if not (object, ids, fields2) in done:
 -                    self.pool.get(object)._store_set_values(cr, user, ids, fields2, context)
 -                    done.append((object, ids, fields2))
 +            for order, model_name, ids, fields2 in result:
 +                if not (model_name, ids, fields2) in done:
 +                    self.pool[model_name]._store_set_values(cr, user, ids, fields2, context)
 +                    done.append((model_name, ids, fields2))
  
          if self._log_create and not (context and context.get('no_store_function', False)):
              message = self._description + \
                  "' " + _("created.")
              self.log(cr, user, id_new, message, True, context=context)
          self.check_access_rule(cr, user, [id_new], 'create', context=context)
 -        self._workflow_trigger(cr, user, [id_new], 'trg_create', context=context)
 +        self.create_workflow(cr, user, [id_new], context=context)
          return id_new
  
      def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
              return browse_null()
  
      def _store_get_values(self, cr, uid, ids, fields, context):
 -        """Returns an ordered list of fields.functions to call due to
 +        """Returns an ordered list of fields.function to call due to
             an update operation on ``fields`` of records with ``ids``,
 -           obtained by calling the 'store' functions of these fields,
 +           obtained by calling the 'store' triggers of these fields,
             as setup by their 'store' attribute.
  
             :return: [(priority, model_name, [record_ids,], [function_fields,])]
          stored_functions = self.pool._store_function.get(self._name, [])
  
          # use indexed names for the details of the stored_functions:
 -        model_name_, func_field_to_compute_, id_mapping_fnct_, trigger_fields_, priority_ = range(5)
 +        model_name_, func_field_to_compute_, target_ids_func_, trigger_fields_, priority_ = range(5)
  
 -        # only keep functions that should be triggered for the ``fields``
 +        # only keep store triggers that should be triggered for the ``fields``
          # being written to.
 -        to_compute = [f for f in stored_functions \
 +        triggers_to_compute = [f for f in stored_functions \
                  if ((not f[trigger_fields_]) or set(fields).intersection(f[trigger_fields_]))]
  
 -        mapping = {}
 -        fresults = {}
 -        for function in to_compute:
 -            fid = id(function[id_mapping_fnct_])
 -            if not fid in fresults:
 +        to_compute_map = {}
 +        target_id_results = {}
 +        for store_trigger in triggers_to_compute:
 +            target_func_id_ = id(store_trigger[target_ids_func_])
 +            if not target_func_id_ in target_id_results:
                  # use admin user for accessing objects having rules defined on store fields
 -                fresults[fid] = [id2 for id2 in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id2]
 -            target_ids = fresults[fid]
 +                target_id_results[target_func_id_] = [i for i in store_trigger[target_ids_func_](self, cr, SUPERUSER_ID, ids, context) if i]
 +            target_ids = target_id_results[target_func_id_]
  
              # the compound key must consider the priority and model name
 -            key = (function[priority_], function[model_name_])
 +            key = (store_trigger[priority_], store_trigger[model_name_])
              for target_id in target_ids:
 -                mapping.setdefault(key, {}).setdefault(target_id,set()).add(tuple(function))
 +                to_compute_map.setdefault(key, {}).setdefault(target_id,set()).add(tuple(store_trigger))
  
 -        # Here mapping looks like:
 -        # { (10, 'model_a') : { target_id1: [ (function_1_tuple, function_2_tuple) ], ... }
 -        #   (20, 'model_a') : { target_id2: [ (function_3_tuple, function_4_tuple) ], ... }
 -        #   (99, 'model_a') : { target_id1: [ (function_5_tuple, function_6_tuple) ], ... }
 +        # Here to_compute_map looks like:
 +        # { (10, 'model_a') : { target_id1: [ (trigger_1_tuple, trigger_2_tuple) ], ... }
 +        #   (20, 'model_a') : { target_id2: [ (trigger_3_tuple, trigger_4_tuple) ], ... }
 +        #   (99, 'model_a') : { target_id1: [ (trigger_5_tuple, trigger_6_tuple) ], ... }
          # }
  
          # Now we need to generate the batch function calls list
          # call_map =
          #   { (10, 'model_a') : [(10, 'model_a', [record_ids,], [function_fields,])] }
          call_map = {}
 -        for ((priority,model), id_map) in mapping.iteritems():
 -            functions_ids_maps = {}
 +        for ((priority,model), id_map) in to_compute_map.iteritems():
 +            trigger_ids_maps = {}
              # function_ids_maps =
              #   { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] }
 -            for fid, functions in id_map.iteritems():
 -                functions_ids_maps.setdefault(tuple(functions), []).append(fid)
 -            for functions, ids in functions_ids_maps.iteritems():
 -                call_map.setdefault((priority,model),[]).append((priority, model, ids,
 -                                                                 [f[func_field_to_compute_] for f in functions]))
 +            for target_id, triggers in id_map.iteritems():
 +                trigger_ids_maps.setdefault(tuple(triggers), []).append(target_id)
 +            for triggers, target_ids in trigger_ids_maps.iteritems():
 +                call_map.setdefault((priority,model),[]).append((priority, model, target_ids,
 +                                                                 [t[func_field_to_compute_] for t in triggers]))
          ordered_keys = call_map.keys()
          ordered_keys.sort()
          result = []
                      # to reach the parent table (if it was not JOINed yet in the query)
                      parent_alias = child_object._inherits_join_add(child_object, parent_model, query)
                      # inherited rules are applied on the external table -> need to get the alias and replace
 -                    parent_table = self.pool.get(parent_model)._table
 +                    parent_table = self.pool[parent_model]._table
                      added_clause = [clause.replace('"%s"' % parent_table, '"%s"' % parent_alias) for clause in added_clause]
                      # change references to parent_table to parent_alias, because we now use the alias to refer to the table
                      new_tables = []
              return
  
          # figure out the applicable order_by for the m2o
 -        dest_model = self.pool.get(order_field_column._obj)
 +        dest_model = self.pool[order_field_column._obj]
          m2o_order = dest_model._order
          if not regex_order.match(m2o_order):
              # _order is complex, can't use it here, so we default to _rec_name
                      else:
                          continue  # ignore non-readable or "non-joinable" fields
                  elif order_field in self._inherit_fields:
 -                    parent_obj = self.pool.get(self._inherit_fields[order_field][3])
 +                    parent_obj = self.pool[self._inherit_fields[order_field][3]]
                      order_column = parent_obj._columns[order_field]
                      if order_column._classic_read:
                          inner_clause = self._inherits_join_calc(order_field, query)
          limit_str = limit and ' limit %d' % limit or ''
          offset_str = offset and ' offset %d' % offset or ''
          where_str = where_clause and (" WHERE %s" % where_clause) or ''
 +        query_str = 'SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str
  
          if count:
 -            cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
 -            res = cr.fetchall()
 -            return res[0][0]
 -        cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
 +            # /!\ the main query must be executed as a subquery, otherwise
 +            # offset and limit apply to the result of count()!
 +            cr.execute('SELECT count(*) FROM (%s) AS count' % query_str, where_clause_params)
 +            res = cr.fetchone()
 +            return res[0]
 +
 +        cr.execute(query_str, where_clause_params)
          res = cr.fetchall()
  
          # TDE note: with auto_join, we could have several lines about the same result
          if not args:
              args = []
          if field in self._inherit_fields:
 -            return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
 +            return self.pool[self._inherit_fields[field][0]].distinct_field_get(cr, uid, field, value, args, offset, limit)
          else:
              return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
  
                  if field_to_other in default:
                      # all the fields of 'other' are given by the record: default[field_to_other],
                      # except the ones redefined in self
 -                    blacklist.update(set(self.pool.get(other)._all_columns) - set(self._columns))
 +                    blacklist.update(set(self.pool[other]._all_columns) - set(self._columns))
                  else:
 -                    blacklist_given_fields(self.pool.get(other))
 +                    blacklist_given_fields(self.pool[other])
          blacklist_given_fields(self)
  
          res = dict(default)
              elif field._type == 'many2one':
                  res[f] = data[f] and data[f][0]
              elif field._type == 'one2many':
 -                other = self.pool.get(field._obj)
 +                other = self.pool[field._obj]
                  # duplicate following the order of the ids because we'll rely on
                  # it later for copying translations in copy_translation()!
                  lines = [other.copy_data(cr, uid, line_id, context=context) for line_id in sorted(data[f])]
          # TODO it seems fields_get can be replaced by _all_columns (no need for translation)
          fields = self.fields_get(cr, uid, context=context)
  
          for field_name, field_def in fields.items():
              # we must recursively copy the translations for o2o and o2m
              if field_def['type'] == 'one2many':
 -                target_obj = self.pool.get(field_def['relation'])
 +                target_obj = self.pool[field_def['relation']]
                  old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
                  # here we rely on the order of the ids to match the translations
                  # as foreseen in copy_data()
                      target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
              # and for translatable fields we keep them for copy
              elif field_def.get('translate'):
-                 trans_name = ''
                  if field_name in self._columns:
                      trans_name = self._name + "," + field_name
+                     res_id = new_id
+                 
                  elif field_name in self._inherit_fields:
                      trans_name = self._inherit_fields[field_name][0] + "," + field_name
-                 if trans_name:
-                     trans_ids = trans_obj.search(cr, uid, [
-                             ('name', '=', trans_name),
-                             ('res_id', '=', old_id)
-                     ])
-                     translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
+                     # get the id of the parent record to set the translation
+                     inherit_field_name = self._inherit_fields[field_name][1]
+                     res_id = self.read(cr, uid, [new_id], [inherit_field_name], context=context)[0][inherit_field_name][0]
+                 else:
+                     continue
  
-         for record in translation_records:
-             del record['id']
-             record['res_id'] = new_id
-             trans_obj.create(cr, uid, record, context=context)
+                 trans_ids = trans_obj.search(cr, uid, [
+                         ('name', '=', trans_name),
+                         ('res_id', '=', old_id)
+                 ])
+                 records = trans_obj.read(cr, uid, trans_ids, context=context)
+                 for record in records:
+                     del record['id']
+                     # remove source to avoid triggering _set_src
+                     del record['source']
+                     record.update({'res_id': res_id})
+                     trans_obj.create(cr, uid, record, context=context)
  
  
      def copy(self, cr, uid, id, default=None, context=None):
      get_xml_id = get_external_id
      _get_xml_ids = _get_external_ids
  
 +    def print_report(self, cr, uid, ids, name, data, context=None):
 +        """
 +        Render the report `name` for the given IDs. The report must be defined
 +        for this model, not another.
 +        """
 +        report = self.pool['ir.actions.report.xml']._lookup_report(cr, name)
 +        assert self._name == report.table
 +        return report.create(cr, uid, ids, data, context)
 +
      # Transience
      def is_transient(self):
          """ Return whether the model is transient.
                  result, record_ids = [], list(command[2])
  
          # read the records and apply the updates
 -        other_model = self.pool.get(self._all_columns[field_name].column._obj)
 +        other_model = self.pool[self._all_columns[field_name].column._obj]
          for record in other_model.read(cr, uid, record_ids, fields=fields, context=context):
              record.update(updates.get(record['id'], {}))
              result.append(record)
          """ stuff to do right after the registry is built """
          pass
  
 +    def __getattr__(self, name):
 +        if name.startswith('signal_'):
 +            signal_name = name[len('signal_'):]
 +            assert signal_name
 +            return (lambda *args, **kwargs:
 +                    self.signal_workflow(*args, signal=signal_name, **kwargs))
 +        get = getattr(super(BaseModel, self), '__getattr__', None)
 +        if get is not None: return get(name)
 +        raise AttributeError(
 +            "'%s' object has no attribute '%s'" % (type(self).__name__, name))
 +
  # keep this import here, at top it will cause dependency cycle errors
  import expression
  
@@@ -5382,7 -5339,8 +5390,7 @@@ def convert_pgerror_23502(model, fields
      message = _(u"Missing required value for the field '%s'.") % field_name
      field = fields.get(field_name)
      if field:
 -        message = _(u"%s This might be '%s' in the current model, or a field "
 -                    u"of the same name in an o2m.") % (message, field['string'])
 +        message = _(u"Missing required value for the field '%s' (%s)") % (field['string'], field_name)
      return {
          'message': message,
          'field': field_name,