[MERGE]merge with trunk.
authorVidhin Mehta <vme@tinyerp.com>
Fri, 17 May 2013 05:41:10 +0000 (11:11 +0530)
committerVidhin Mehta <vme@tinyerp.com>
Fri, 17 May 2013 05:41:10 +0000 (11:11 +0530)
bzr revid: vme@tinyerp.com-20130517054110-vej1co0gtacht4x4

32 files changed:
1  2 
addons/web/__openerp__.py
addons/web/controllers/main.py
addons/web/static/lib/jquery.placeholder/jquery.placeholder.js
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/fixbind.js
addons/web/static/src/font/entypo-webfont.eot
addons/web/static/src/font/entypo-webfont.svg
addons/web/static/src/font/entypo-webfont.ttf
addons/web/static/src/font/entypo-webfont.woff
addons/web/static/src/font/mnmliconsv21-webfont.eot
addons/web/static/src/font/mnmliconsv21-webfont.svg
addons/web/static/src/font/mnmliconsv21-webfont.ttf
addons/web/static/src/font/mnmliconsv21-webfont.woff
addons/web/static/src/js/chrome.js
addons/web/static/src/js/data.js
addons/web/static/src/js/formats.js
addons/web/static/src/js/search.js
addons/web/static/src/js/testing.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/view_list.js
addons/web/static/src/js/views.js
addons/web/static/src/xml/base.xml
addons/web/static/test/data.js
addons/web/static/test/search.js
addons/web_calendar/static/src/js/calendar.js
addons/web_diagram/controllers/main.py
addons/web_kanban/static/src/css/kanban.css
addons/web_kanban/static/src/css/kanban.sass
addons/web_kanban/static/src/js/kanban.js
addons/web_kanban/static/src/xml/web_kanban.xml
addons/web_view_editor/static/src/js/view_editor.js

@@@ -13,6 -13,6 +13,7 @@@ This module provides the core of the Op
      'auto_install': True,
      'post_load': 'wsgi_postload',
      'js' : [
++        "static/src/fixbind.js",
          "static/lib/datejs/globalization/en-US.js",
          "static/lib/datejs/core.js",
          "static/lib/datejs/parser.js",
          "static/lib/spinjs/spin.js",
          "static/lib/jquery.autosize/jquery.autosize.js",
          "static/lib/jquery.blockUI/jquery.blockUI.js",
+         "static/lib/jquery.placeholder/jquery.placeholder.js",
          "static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js",
          "static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js",
 +        "static/lib/jquery.ui.timepicker/js/jquery-ui-sliderAccess.js",
          "static/lib/jquery.ui.notify/js/jquery.notify.js",
          "static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
          "static/lib/jquery.scrollTo/jquery.scrollTo-min.js",
@@@ -76,6 -76,6 +78,7 @@@
          "static/test/class.js",
          "static/test/registry.js",
          "static/test/form.js",
++        "static/test/data.js",
          "static/test/list-utils.js",
          "static/test/formats.js",
          "static/test/rpc.js",
@@@ -603,6 -643,6 +643,18 @@@ class WebClient(openerpweb.Controller)
  
          content, checksum = concat_files((f[0] for f in files), reader)
  
++        # move up all @import and @charset rules to the top
++        matches = []
++        def push(matchobj):
++            matches.append(matchobj.group(0))
++            return ''
++
++        content = re.sub(re.compile("(@charset.+;$)", re.M), push, content)
++        content = re.sub(re.compile("(@import.+;$)", re.M), push, content)
++
++        matches.append(content)
++        content = '\n'.join(matches)
++
          return make_conditional(
              req, req.make_response(content, [('Content-Type', 'text/css')]),
              last_modified, checksum)
@@@ -1323,19 -1363,19 +1375,30 @@@ class Binary(openerpweb.Controller)
          elif dbname is None:
              dbname = db_monodb(req)
  
--        if uid is None:
++        if not uid:
              uid = openerp.SUPERUSER_ID
  
          if not dbname:
              image_data = self.placeholder(req, 'logo.png')
          else:
--            registry = openerp.modules.registry.RegistryManager.get(dbname)
--            with registry.cursor() as cr:
--                user = registry.get('res.users').browse(cr, uid, uid)
--                if user.company_id.logo_web:
--                    image_data = user.company_id.logo_web.decode('base64')
--                else:
--                    image_data = self.placeholder(req, 'nologo.png')
++            try:
++                # create an empty registry
++                registry = openerp.modules.registry.Registry(dbname.lower())
++                with registry.cursor() as cr:
++                    cr.execute("""SELECT c.logo_web
++                                    FROM res_users u
++                               LEFT JOIN res_company c
++                                      ON c.id = u.company_id
++                                   WHERE u.id = %s
++                               """, (uid,))
++                    row = cr.fetchone()
++                    if row and row[0]:
++                        image_data = str(row[0]).decode('base64')
++                    else:
++                        image_data = self.placeholder(req, 'nologo.png')
++            except Exception:
++                image_data = self.placeholder(req, 'logo.png')
++
          headers = [
              ('Content-Type', 'image/png'),
              ('Content-Length', len(image_data)),
@@@ -1380,7 -1420,7 +1443,7 @@@ class Action(openerpweb.Controller)
          else:
              return False
  
--class Export(View):
++class Export(openerpweb.Controller):
      _cp_path = "/web/export"
  
      @openerpweb.jsonrequest
              (prefix + '/' + k, prefix_string + '/' + v)
              for k, v in self.fields_info(req, model, export_fields).iteritems())
  
--    #noinspection PyPropertyDefinition
++class ExportFormat(object):
      @property
      def content_type(self):
          """ Provides the format's content type """
                       ('Content-Type', self.content_type)],
              cookies={'fileToken': int(token)})
  
--class CSVExport(Export):
++class CSVExport(ExportFormat, http.Controller):
      _cp_path = '/web/export/csv'
      fmt = {'tag': 'csv', 'label': 'CSV'}
  
          fp.close()
          return data
  
--class ExcelExport(Export):
++class ExcelExport(ExportFormat, http.Controller):
      _cp_path = '/web/export/xls'
      fmt = {
          'tag': 'xls',
          fp.close()
          return data
  
--class Reports(View):
++class Reports(openerpweb.Controller):
      _cp_path = "/web/report"
      POLLING_DELAY = 0.25
      TYPES_MAPPING = {
Simple merge
@@@ -123,6 -123,6 +123,7 @@@ $sheet-padding: 16p
      font-size: 1px
      letter-spacing: -1px
      color: transparent
++    text-shadow: none
      font-weight: normal
      &:before
          font: 21px "mnmliconsRegular"
      font-size: 1px
      letter-spacing: -1px
      color: transparent
++    text-shadow: none
      font-weight: normal
      &:before
          font: $size "entypoRegular"
              .oe_form_field_float input
                  width: 7em
              .oe_form_field_date input
--                width: 7.5em
++                width: 100px
              .oe_form_field_datetime input
--                width: 11.5em
++                width: 150px
      // }}}
      // FormView.fields_binary {{{
      /* http://www.quirksmode.org/dom/inputfile.html
@@@ -2479,8 -2548,16 +2551,18 @@@ div.ui-widget-overla
      @include radius(3px)
  // }}}
  
+ .openerp
+     .db_option_table
+         td
+             padding-bottom: 10px !important
  // Internet Explorer 9+ specifics {{{
  .openerp_ie
+     .placeholder
+         color: $tag-border !important
+         font-style: italic !important
++    .oe_form_binary_file
++        width: 80px
      .oe_form_field_boolean input
          background: #fff
      .db_option_table .oe_form_field_selection
index 0000000,0000000..4a441ce
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,28 @@@
++// Fix old versions of Webkit (such as ones used on iOS < 6 or PhantomJS <= 1.7)
++// which does not have Function.prototype.bind function
++
++// Use moz polyfill:
++// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
++if (!Function.prototype.bind) {
++  Function.prototype.bind = function (oThis) {
++    if (typeof this !== "function") {
++      // closest thing possible to the ECMAScript 5 internal IsCallable function
++      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
++    }
++
++    var aArgs = Array.prototype.slice.call(arguments, 1),
++        fToBind = this,
++        fNOP = function () {},
++        fBound = function () {
++          return fToBind.apply(this instanceof fNOP && oThis
++                                 ? this
++                                 : oThis,
++                               aArgs.concat(Array.prototype.slice.call(arguments)));
++        };
++
++    fNOP.prototype = this.prototype;
++    fBound.prototype = new fNOP();
++
++    return fBound;
++  };
++}
index 2c09f33,2c09f33..2c09f33
mode 100755,100755..100644
Binary files differ
index 89b7a19,89b7a19..89b7a19
mode 100755,100755..100644
Binary files differ
index 3879f5b,3879f5b..3879f5b
mode 100755,100755..100644
Binary files differ
index 433ebb8,433ebb8..433ebb8
mode 100755,100755..100644
Binary files differ
index c38e59c,c38e59c..c38e59c
mode 100755,100755..100644
Binary files differ
index 3ddf14f,3ddf14f..3ddf14f
mode 100755,100755..100644
Binary files differ
@@@ -251,9 -251,9 +251,9 @@@ instance.web.CrashManager = instance.we
          if (handler) {
              new (handler)(this, error).display();
              return;
--        };
++        }
          if (error.data.name === "openerp.addons.web.session SessionExpiredException") {
--            this.show_warning({type: "Session Expired", data: { message: "Your OpenERP session expired. Please refresh the current web page." }});
++            this.show_warning({type: "Session Expired", data: { message: _t("Your OpenERP session expired. Please refresh the current web page.") }});
              return;
          }
          if (error.data.exception_type === "except_osv" || error.data.exception_type === "warning"
@@@ -502,7 -530,16 +530,11 @@@ instance.web.DatabaseManager = instance
                      'login': 'admin',
                      'password': form_obj['create_admin_pwd'],
                      'login_successful': function() {
-                         self.do_action("reload");
 -                        var action = {
 -                            type: "ir.actions.client",
 -                            tag: 'reload',
 -                            params: {
 -                                url_search : {
 -                                    db: form_obj['db_name'],
 -                                },
 -                            }
 -                        };
 -                        self.do_action(action);
++                        var url = '/?db=' + form_obj['db_name'];
++                        if (self.session.debug) {
++                            url += '&debug';
++                        }
++                        instance.web.redirect(url);
                      },
                  },
                  _push_me: false,
@@@ -112,24 -112,24 +112,27 @@@ instance.web.Query = instance.web.Class
       * @returns {jQuery.Deferred<Array<openerp.web.QueryGroup>> | null}
       */
      group_by: function (grouping) {
--        if (grouping === undefined) {
--            return null;
++        var ctx = instance.web.pyeval.eval(
++            'context', this._model.context(this._context));
++
++        // undefined passed in explicitly (!)
++        if (_.isUndefined(grouping)) {
++            grouping = [];
          }
  
          if (!(grouping instanceof Array)) {
              grouping = _.toArray(arguments);
          }
--        if (_.isEmpty(grouping)) { return null; }
++        if (_.isEmpty(grouping) && !ctx['group_by_no_leaf']) {
++            return null;
++        }
  
          var self = this;
--
--        var ctx = instance.web.pyeval.eval(
--            'context', this._model.context(this._context));
          return this._model.call('read_group', {
              groupby: grouping,
              fields: _.uniq(grouping.concat(this._fields || [])),
              domain: this._model.domain(this._filter),
--            context: this._model.context(this._context),
++            context: ctx,
              offset: this._offset,
              limit: this._limit,
              orderby: instance.web.serialize_sort(this._order_by) || false
@@@ -325,7 -325,7 +328,7 @@@ instance.web.Model = instance.web.Class
       * Fetches the model's domain, combined with the provided domain if any
       *
       * @param {Array} [domain] to combine with the model's internal domain
--     * @returns The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain
++     * @returns {instance.web.CompoundDomain} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain
       */
      domain: function (domain) {
          if (!domain) { return this._domain; }
       * combined with the provided context if any
       *
       * @param {Object} [context] to combine with the model's internal context
--     * @returns The union of the user's context and the model's internal context, as well as the provided context if any. In that order.
++     * @returns {instance.web.CompoundContext} The union of the user's context and the model's internal context, as well as the provided context if any. In that order.
       */
      context: function (context) {
          return new instance.web.CompoundContext(
@@@ -599,6 -604,6 +607,9 @@@ instance.web.DataSet =  instance.web.Cl
      alter_ids: function(n_ids) {
          this.ids = n_ids;
      },
++    remove_ids: function (ids) {
++        this.alter_ids(_(this.ids).difference(ids));
++    },
      /**
       * Resequence records.
       *
@@@ -696,22 -701,22 +707,28 @@@ instance.web.DataSetSearch =  instance.
      get_domain: function (other_domain) {
          this._model.domain(other_domain);
      },
++    alter_ids: function (ids) {
++        this._super(ids);
++        if (this.index !== null && this.index >= this.ids.length) {
++            this.index = this.ids.length > 0 ? this.ids.length - 1 : 0;
++        }
++    },
++    remove_ids: function (ids) {
++        var before = this.ids.length;
++        this._super(ids);
++        if (this._length) {
++            this._length -= (before - this.ids.length);
++        }
++    },
      unlink: function(ids, callback, error_callback) {
          var self = this;
          return this._super(ids).done(function(result) {
--            self.ids = _(self.ids).difference(ids);
--            if (self._length) {
--                self._length -= 1;
--            }
--            if (self.index !== null) {
--                self.index = self.index <= self.ids.length - 1 ?
--                    self.index : (self.ids.length > 0 ? self.ids.length -1 : 0);
--            }
++            self.remove_ids( ids);
              self.trigger("dataset_changed", ids, callback, error_callback);
          });
      },
      size: function () {
--        if (this._length !== undefined) {
++        if (this._length != null) {
              return this._length;
          }
          return this._super();
@@@ -314,4 -314,4 +314,34 @@@ instance.web.auto_date_to_str = functio
      }
  };
  
++/**
++ * performs a half up rounding with arbitrary precision, correcting for float loss of precision
++ * See the corresponding float_round() in server/tools/float_utils.py for more info
++ * @param {Number} the value to be rounded
++ * @param {Number} a non zero precision parameter. eg: 0.01 rounds to two digits.
++ */
++instance.web.round_precision = function(value, precision){
++    if(!value){
++        return 0;
++    }else if(!precision){
++        throw new Error('round_precision(...):  Cannot round value: '+value+' with a precision of zero (or undefined)');
++    }
++    var normalized_value = value / precision;
++    var epsilon_magnitude = Math.log(Math.abs(normalized_value))/Math.log(2);
++    var epsilon = Math.pow(2, epsilon_magnitude - 53);
++    normalized_value += normalized_value >= 0 ? epsilon : -epsilon;
++    var rounded_value = Math.round(normalized_value);
++    return rounded_value * precision;
++};
++
++/**
++ * performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision
++ * See the corresponding float_round() in server/tools/float_utils.py for more info
++ * @param {Number} the value to be rounded
++ * @param {Number} the number of decimals. eg: round_decimals(3.141592,2) -> 3.14
++ */
++instance.web.round_decimals = function(value, decimals){
++    return instance.web.round_precision(value, Math.pow(10,-decimals));
++};
++
  };
@@@ -324,7 -324,10 +324,10 @@@ instance.web.SearchView = instance.web.
                  e.preventDefault();
                  break;
              }
-         }
+         },
+         'autocompleteopen': function () {
 -            this.$el.autocomplete('widget').css('z-index', 3);
++            this.$el.autocomplete('widget').css('z-index', 1004);
+         },
      },
      /**
       * @constructs instance.web.SearchView
@@@ -997,7 -1039,7 +1039,9 @@@ instance.web.search.FilterGroup = insta
          facet.values.each(function (v) {
              var i = _(self.filters).indexOf(v.get('value'));
              if (i === -1) { return; }
--            $filters.eq(i).addClass('oe_selected');
++            $filters.filter(function () {
++                return Number($(this).data('index')) === i;
++            }).addClass('oe_selected');
          });
      },
      /**
          });
      },
      toggle_filter: function (e) {
--        this.toggle(this.filters[$(e.target).index()]);
++        this.toggle(this.filters[Number($(e.target).data('index'))]);
      },
      toggle: function (filter) {
          this.view.query.toggle(this.make_facet([this.make_value(filter)]));
@@@ -1294,20 -1337,20 +1339,22 @@@ instance.web.search.CharField = instanc
      }
  });
  instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{
--    value_from: function () {
--        if (!this.$el.val()) {
--            return null;
--        }
--        var val = this.parse(this.$el.val()),
--          check = Number(this.$el.val());
--        if (isNaN(val) || val !== check) {
--            this.$el.addClass('error');
--            throw new instance.web.search.Invalid(
--                this.attrs.name, this.$el.val(), this.error_message);
--        }
--        this.$el.removeClass('error');
--        return val;
--    }
++    complete: function (value) {
++        var val = this.parse(value);
++        if (isNaN(val)) { return $.when(); }
++        var label = _.str.sprintf(
++            _t("Search %(field)s for: %(value)s"), {
++                field: '<em>' + this.attrs.string + '</em>',
++                value: '<strong>' + _.str.escapeHTML(value) + '</strong>'});
++        return $.when([{
++            label: label,
++            facet: {
++                category: this.attrs.string,
++                field: this,
++                values: [{label: value, value: val}]
++            }
++        }]);
++    },
  });
  /**
   * @class
@@@ -176,9 -176,9 +176,9 @@@ openerp.testing = {}
          });
  
          QUnit.module(testing.current_module + '.' + name, {_oe: options});
--        body(testing.case);
++        body(testing['case']);
      };
--    testing.case = function (name, options, callback) {
++    testing['case'] = function (name, options, callback) {
          if (_.isFunction(options)) {
              callback = options;
              options = {};
@@@ -763,7 -765,7 +765,11 @@@ instance.web.FormView = instance.web.Vi
          this.has_been_loaded.done(function() {
              if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
                  self.dataset.unlink([self.datarecord.id]).done(function() {
--                    self.execute_pager_action('next');
++                    if (self.dataset.size()) {
++                        self.execute_pager_action('next');
++                    } else {
++                        self.do_action('history_back');
++                    }
                      def.resolve();
                  });
              } else {
              if (save_obj.error)
                  return $.Deferred().reject();
              return $.when.apply($, save_obj.ret);
++        }).done(function() {
++            self.$el.removeClass('oe_form_dirty');
          });
      },
      _process_save: function(save_obj) {
              return value;
          }
          var fields = _.chain(this.fields)
--            .map(function (field, name) {
++            .map(function (field) {
                  var value = field.get_value();
                  // ignore fields which are empty, invisible, readonly, o2m
                  // or m2m
                  }
  
                  return {
--                    name: name,
++                    name: field.name,
                      string: field.string,
                      value: value,
                      displayed: display(field, value),
              .value();
          var conditions = _.chain(self.fields)
              .filter(function (field) { return field.field.change_default; })
--            .map(function (field, name) {
++            .map(function (field) {
                  var value = field.get_value();
                  return {
--                    name: name,
++                    name: field.name,
                      string: field.string,
                      value: value,
                      displayed: display(field, value),
@@@ -1433,6 -1444,6 +1450,9 @@@ instance.web.form.FormRenderingEngine 
              $(this).children().each(function() {
                  var $td = $(this),
                      $child = $td.children(':first');
++                if ($child.attr('cell-class')) {
++                    $td.addClass($child.attr('cell-class'));
++                }
                  switch ($child[0].tagName.toLowerCase()) {
                      case 'separator':
                          break;
              if (! page.__ic)
                  return;
              page.__ic.on("change:effective_invisible", null, function() {
--                if (!page.__ic.get('effective_invisible')) {
++                if (!page.__ic.get('effective_invisible') && page.autofocus) {
                      $new_notebook.tabs('select', i);
                      return;
                  }
@@@ -2112,7 -2126,7 +2135,7 @@@ instance.web.form.AbstractField = insta
          value without triggering a re-rendering.
      */
      internal_set_value: function(value_) {
--        var tmp = this.no_render;
++        var tmp = this.no_rerender;
          this.no_rerender = true;
          this.set({'value': value_});
          this.no_rerender = tmp;
@@@ -2270,7 -2296,7 +2305,8 @@@ instance.web.form.FieldChar = instance.
          return this.get('value') === '' || this._super();
      },
      focus: function() {
--        this.$('input:first')[0].focus();
++        var input = this.$('input:first')[0];
++        return input ? input.focus() : false;
      },
      set_dimensions: function (height, width) {
          this._super(height, width);
@@@ -2366,7 -2393,7 +2403,8 @@@ instance.web.form.FieldFloat = instance
          this._super.apply(this, [value_]);
      },
      focus: function () {
--        this.$('input:first').select();
++        var $input = this.$('input:first');
++        return $input.length ? $input.select() : false;
      }
  });
  
@@@ -2391,16 -2420,13 +2429,57 @@@ instance.web.DateTimeWidget = instance.
              changeYear: true,
              showWeek: true,
              showButtonPanel: true,
 -            firstDay: Date.CultureInfo.firstDayOfWeek
 +            firstDay: Date.CultureInfo.firstDayOfWeek,
 +            controlType:  value,
 +        };
 +    },
 +    start: function() {
 +        var self = this;
 +        this.$input = this.$el.find('input.oe_datepicker_master');
 +        this.$input_picker = this.$el.find('input.oe_datepicker_container');
 +
++        $.datepicker.setDefaults({
++            clearText: _t('Clear'),
++            clearStatus: _t('Erase the current date'),
++            closeText: _t('Done'),
++            closeStatus: _t('Close without change'),
++            prevText: _t('<Prev'),
++            prevStatus: _t('Show the previous month'),
++            nextText: _t('Next>'),
++            nextStatus: _t('Show the next month'),
++            currentText: _t('Today'),
++            currentStatus: _t('Show the current month'),
++            monthNames: Date.CultureInfo.monthNames,
++            monthNamesShort: Date.CultureInfo.abbreviatedMonthNames,
++            monthStatus: _t('Show a different month'),
++            yearStatus: _t('Show a different year'),
++            weekHeader: _t('Wk'),
++            weekStatus: _t('Week of the year'),
++            dayNames: Date.CultureInfo.dayNames,
++            dayNamesShort: Date.CultureInfo.abbreviatedDayNames,
++            dayNamesMin: Date.CultureInfo.shortestDayNames,
++            dayStatus: _t('Set DD as first week day'),
++            dateStatus: _t('Select D, M d'),
++            firstDay: Date.CultureInfo.firstDayOfWeek,
++            initStatus: _t('Select a date'),
++            isRTL: false
+         });
++        $.timepicker.setDefaults({
++            timeOnlyTitle: _t('Choose Time'),
++            timeText: _t('Time'),
++            hourText: _t('Hour'),
++            minuteText: _t('Minute'),
++            secondText: _t('Second'),
++            currentText: _t('Now'),
++            closeText: _t('Done')
++        });
++
 +        this.picker(this.config_plugin());
+         // Some clicks in the datepicker dialog are not stopped by the
+         // datepicker and "bubble through", unexpectedly triggering the bus's
+         // click event. Prevent that.
+         this.picker('widget').click(function (e) { e.stopPropagation(); });
          this.$el.find('img.oe_datepicker_trigger').click(function() {
              if (self.get("effective_readonly") || self.picker('widget').is(':visible')) {
                  self.$input.focus();
@@@ -2513,9 -2539,9 +2592,8 @@@ instance.web.form.FieldDatetime = insta
          return this.get('value') === '' || this._super();
      },
      focus: function() {
--        if (this.datewidget && this.datewidget.$input) {
--            this.datewidget.$input[0].focus();
--        }
++        var input = this.datewidget && this.datewidget.$input[0];
++        return input ? input.focus() : false;
      },
      set_dimensions: function (height, width) {
          this._super(height, width);
@@@ -2593,7 -2622,9 +2674,8 @@@ instance.web.form.FieldText = instance.
          return this.get('value') === '' || this._super();
      },
      focus: function($el) {
-         this.$textarea[0].focus();
 -        if (!this.get("effective_readonly") && this.$textarea) {
 -            this.$textarea[0].focus();
 -        }
++        var input = !this.get("effective_readonly") && this.$textarea && this.$textarea[0];
++        return input ? input.focus() : false;
      },
      set_dimensions: function (height, width) {
          this._super(height, width);
@@@ -2674,7 -2707,7 +2758,8 @@@ instance.web.form.FieldBoolean = instan
          this.$checkbox[0].checked = this.get('value');
      },
      focus: function() {
--        this.$checkbox[0].focus();
++        var input = this.$checkbox && this.$checkbox[0];
++        return input ? input.focus() : false;
      }
  });
  
@@@ -2760,7 -2793,7 +2845,8 @@@ instance.web.form.FieldSelection = inst
          }
      },
      focus: function() {
--        this.$('select:first')[0].focus();
++        var input = this.$('select:first')[0];
++        return input ? input.focus() : false;
      },
      set_dimensions: function (height, width) {
          this._super(height, width);
@@@ -3053,9 -3199,9 +3252,9 @@@ instance.web.form.FieldMany2One = insta
          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.autocomplete("close");
-                 self.$input.focus();
              } else {
                  if (self.get("value") && ! self.floating) {
                      self.$input.autocomplete("search", "");
          }
          if (! no_recurse) {
              var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
--            dataset.name_get([self.get("value")]).done(function(data) {
++            this.alive(dataset.name_get([self.get("value")])).done(function(data) {
                  self.display_value["" + self.get("value")] = data[0][1];
                  self.render_value(true);
              });
          return ! this.get("value");
      },
      focus: function () {
--        if (!this.get('effective_readonly')) {
-             this.$input[0].focus();
-         }
 -            this.$input && this.$input[0].focus();
 -        }
++        var input = !this.get('effective_readonly') && this.$input && this.$input[0];
++        return input ? input.focus() : false;
      },
      _quick_create: function() {
          this.no_ed = true;
@@@ -4098,7 -4263,14 +4315,15 @@@ instance.web.form.FieldMany2ManyTags = 
          this.set({'value': _.uniq(this.get('value').concat([id]))});
      },
      focus: function () {
--        this.$text[0].focus();
++        var input = this.$text && this.$text[0];
++        return input ? input.focus() : false;
+     },
+     set_dimensions: function (height, width) {
 -        this._super(height, width);        
++        this._super(height, width);
+         this.$("textarea").css({
+             width: width,
+             minHeight: height
+         });
      },
  });
  
@@@ -5240,15 -5386,20 +5439,20 @@@ instance.web.form.FieldStatus = instanc
      },
      render_value: function() {
          var self = this;
-         self.get_selection().done(function() {
-             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);
-             }
-         });
+         var content = QWeb.render("FieldStatus.content", {widget: self});
+         self.$el.html(content);
+     },
+     calc_domain: function() {
+         var d = instance.web.pyeval.eval('domain', this.build_domain());
+         var domain = []; //if there is no domain defined, fetch all the records
 -        
++
+         if (d.length) {
+             domain = ['|',['id', '=', this.get('value')]].concat(d);
+         }
 -        
++
+         if (! _.isEqual(domain, this.get("evaluated_selection_domain"))) {
+             this.set("evaluated_selection_domain", domain);
+         }
      },
      /** Get the selection and render it
       *  selection: [[identifier, value_to_display], ...]
@@@ -578,7 -578,7 +578,7 @@@ instance.web.ListView = instance.web.Vi
          this.no_leaf = !!context['group_by_no_leaf'];
          this.grouped = !!group_by;
  
--        return this.load_view(context).then(
++        return this.alive(this.load_view(context)).then(
              this.proxy('reload_content'));
      },
      /**
@@@ -897,8 -895,8 +897,9 @@@ instance.web.ListView.List = instance.w
  
          this.record_callbacks = {
              'remove': function (event, record) {
--                var $row = self.$current.children(
--                    '[data-id=' + record.get('id') + ']');
++                var id = record.get('id');
++                self.dataset.remove_ids([id])
++                var $row = self.$current.children('[data-id=' + id + ']');
                  var index = $row.data('index');
                  $row.remove();
              },
@@@ -943,7 -1004,7 +1004,7 @@@ instance.web.ViewManagerAction = instan
      switch_mode: function (view_type, no_store, options) {
          var self = this;
  
--        return $.when(this._super.apply(this, arguments)).done(function () {
++        return this.alive($.when(this._super.apply(this, arguments))).done(function () {
              var controller = self.views[self.active_view].controller;
              self.$el.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', {
                  view: controller,
                      <li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
                          <t t-call="HiddenInputFile">
                              <t t-set="fileupload_id" t-value="widget.fileupload_id"/>
--                            <t t-set="fileupload_action">/web/binary/upload_attachment</t>
++                            <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
                              <input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
                              <input type="hidden" name="id" t-att-value="widget.model_id"/>
                              <input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
      <t t-esc="attrs.string"/>
  </button>
  <ul t-name="SearchView.filters">
-     <li t-foreach="widget.filters" t-as="filter"
-             t-att-title="filter.attrs.string ? filter.attrs.help : undefined">
 -    <li t-foreach="widget.filters" t-as="filter" t-if="filter.visible()"
 -            t-att-title="filter.attrs.string ? filter.attrs.help : undefined">
++    <li t-foreach="widget.filters" t-as="filter" t-if="!filter.visible || filter.visible()"
++            t-att-title="filter.attrs.string ? filter.attrs.help : undefined"
++            t-att-data-index="filter_index">
          <t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>
      </li>
  </ul>
index 0000000,0000000..6f1e518
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,76 @@@
++openerp.testing.section('data.model.group_by', {
++    rpc: 'mock',
++    dependencies: ['web.data'],
++}, function (test) {
++    var group_result = [{
++        bar: 3, bar_count: 5, __context: {}, __domain: [['bar', '=', 3]],
++    }, {
++        bar: 5, bar_count: 3, __context: {}, __domain: [['bar', '=', 5]],
++    }, {
++        bar: 8, bar_count: 0, __context: {}, __domain: [['bar', '=', 8]],
++    }];
++    test('basic', {asserts: 7}, function (instance, $fix, mock) {
++        var m = new instance.web.Model('foo');
++        mock('foo:read_group', function (args, kwargs) {
++            deepEqual(kwargs.fields, ['bar'],
++                      "should read grouping field");
++            deepEqual(kwargs.groupby, ['bar'],
++                      "should have single grouping field");
++            return group_result;
++        });
++        mock('/web/dataset/search_read', function (args) {
++            deepEqual(args.params.domain, [['bar', '=', 3]],
++                      "should have domain matching that of group_by result");
++            return {records: [
++                {bar: 3, id: 1},
++                {bar: 3, id: 2},
++                {bar: 3, id: 4},
++                {bar: 3, id: 8},
++                {bar: 3, id: 16}
++            ], length: 5};
++        });
++
++        return m.query().group_by('bar')
++        .then(function (groups) {
++            ok(groups, "should have data");
++            equal(groups.length, 3, "should have three results");
++            var first = groups[0];
++            ok(first.attributes.has_children, "should have children");
++            return  first.query().all();
++        }).done(function (first) {
++            equal(first.length, 5, "should have 5 records")
++        });
++    });
++    test('noleaf', {asserts: 5}, function (instance, $fix, mock) {
++        var m = new instance.web.Model('foo', {group_by_no_leaf: true});
++        mock('foo:read_group', function (args, kwargs) {
++            deepEqual(kwargs.fields, ['bar'],
++                      "should read grouping field");
++            deepEqual(kwargs.groupby, ['bar'],
++                      "should have single grouping field");
++
++            return  group_result;
++        });
++        return m.query().group_by('bar')
++        .then(function (groups) {
++            ok(groups, "should have data");
++            equal(groups.length, 3, "should have three results");
++            ok(!groups[0].attributes.has_children,
++                "should not have children because no_leaf");
++        })
++    });
++    test('nogroup', {rpc: false}, function (instance, $f, mock) {
++        var m = new instance.web.Model('foo');
++        strictEqual(m.query().group_by(), null, "should not group");
++    });
++    test('empty.noleaf', {asserts: 1}, function (instance, $f, mock) {
++        var m = new instance.web.Model('foo',  {group_by_no_leaf: true});
++        mock('foo:read_group', function (args, kwargs) {
++            return [{__context: [], __domain: []}];
++        });
++        return m.query().group_by().done(function (groups) {
++            strictEqual(groups.length, 1,
++                        "should generate a single fake-ish group");
++        });
++    });
++});
@@@ -563,8 -594,28 +594,81 @@@ openerp.testing.section('search.complet
                  ok(!c, "no match should yield no completion");
              });
      });
+     test("M2O filtered", {asserts: 2}, function (instance, $s, mock) {
+         mock('dummy.model:name_search', function (args, kwargs) {
+             deepEqual(args, [], "should have no positional arguments");
+             deepEqual(kwargs, {
+                 name: 'bob',
+                 limit: 8,
+                 args: [['foo', '=', 'bar']],
+                 context: {flag: 1},
+             }, "should use filtering domain");
+             return [[42, "Match"]];
+         });
+         var view = {
+             inputs: [],
+             dataset: {get_context: function () { return {flag: 1}; }}
+         };
+         var f = new instance.web.search.ManyToOneField(
+             {attrs: {string: 'Dummy', domain: '[["foo", "=", "bar"]]'}},
+             {relation: 'dummy.model'}, view);
+         return f.complete("bob");
+     });
++    test('Integer: invalid', {asserts: 1}, function (instance) {
++        var view = {inputs: []};
++        var f = new instance.web.search.IntegerField(
++            {attrs: {string: "Dummy"}}, {}, view);
++        return f.complete("qux")
++            .done(function (completions) {
++                ok(!completions, "non-number => no completion");
++            });
++    });
++    test('Integer: non-zero', {asserts: 5}, function (instance) {
++        var view = {inputs: []};
++        var f = new instance.web.search.IntegerField(
++            {attrs: {string: "Dummy"}}, {}, view);
++        return f.complete("-2")
++            .done(function (completions) {
++                equal(completions.length, 1, "number fields provide 1 completion only");
++                var facet = new instance.web.search.Facet(completions[0].facet);
++                equal(facet.get('category'), f.attrs.string);
++                equal(facet.get('field'), f);
++                var value = facet.values.at(0);
++                equal(value.get('label'), "-2");
++                equal(value.get('value'), -2);
++            });
++    });
++    test('Integer: zero', {asserts: 3}, function (instance) {
++        var view = {inputs: []};
++        var f = new instance.web.search.IntegerField(
++            {attrs: {string: "Dummy"}}, {}, view);
++        return f.complete("0")
++            .done(function (completions) {
++                equal(completions.length, 1, "number fields provide 1 completion only");
++                var facet = new instance.web.search.Facet(completions[0].facet);
++                var value = facet.values.at(0);
++                equal(value.get('label'), "0");
++                equal(value.get('value'), 0);
++            });
++    });
++    test('Float: non-zero', {asserts: 5}, function (instance) {
++        var view = {inputs: []};
++        var f = new instance.web.search.FloatField(
++            {attrs: {string: "Dummy"}}, {}, view);
++        return f.complete("42.37")
++            .done(function (completions) {
++                equal(completions.length, 1, "float fields provide 1 completion only");
++                var facet = new instance.web.search.Facet(completions[0].facet);
++                equal(facet.get('category'), f.attrs.string);
++                equal(facet.get('field'), f);
++                var value = facet.values.at(0);
++                equal(value.get('label'), "42.37");
++                equal(value.get('value'), 42.37);
++            });
++    });
++    
  });
- openerp.testing.section('search-serialization', {
+ openerp.testing.section('search.serialization', {
      dependencies: ['web.search'],
      rpc: 'mock',
      templates: true
@@@ -1120,3 -1328,159 +1381,179 @@@ openerp.testing.section('search.advance
      });
      // TODO: UI tests?
  });
+ openerp.testing.section('search.invisible', {
+     dependencies: ['web.search'],
+     rpc: 'mock',
+     templates: true,
+ }, function (test) {
+     var registerTestField = function (instance, methods) {
+         instance.web.search.fields.add('test', 'instance.testing.TestWidget');
+         instance.testing = {
+             TestWidget: instance.web.search.Field.extend(methods),
+         };
+     };
+     var makeView = function (instance, mock, fields, arch, defaults) {
+         mock('ir.filters:get_filters', function () { return []; });
+         mock('test.model:fields_get', function () { return fields; });
+         mock('test.model:fields_view_get', function () {
+             return { type: 'search', fields: fields, arch: arch };
+         });
+         var ds = new instance.web.DataSet(null, 'test.model');
+         return new instance.web.SearchView(null, ds, false, defaults);
+     };
+     // Invisible fields should not auto-complete
+     test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) {
+         registerTestField(instance, {
+             complete: function () {
+                 return $.when([{label: this.attrs.string}]);
+             },
+         });
+         var view = makeView(instance, mock, {
+             field0: {type: 'test', string: 'Field 0'},
+             field1: {type: 'test', string: 'Field 1'},
+         }, ['<search>',
+                 '<field name="field0"/>',
+                 '<field name="field1" modifiers="{&quot;invisible&quot;: true}"/>',
 -            '</search>'].join());
++            '</search>'].join(''));
+         return view.appendTo($fix)
+         .then(function () {
+             var done = $.Deferred();
+             view.complete_global_search({term: 'test'}, function (comps) {
+                 done.resolve(comps);
+             });
+             return done;
+         }).then(function (completions) {
+             deepEqual(completions, [{label: 'Field 0'}],
+                       "should only complete the visible field");
+         });
+     });
+     // Invisible filters should not appear in the drawer
+     test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) {
+         var view = makeView(instance, mock, {}, [
+             '<search>',
+                 '<filter string="filter 0"/>',
+                 '<filter string="filter 1" modifiers="{&quot;invisible&quot;: true}"/>',
 -            '</search>'].join());
++            '</search>'].join(''));
+         return view.appendTo($fix)
+         .then(function () {
+             var $fs = $fix.find('.oe_searchview_filters ul');
+             strictEqual($fs.children().length,
+                         1,
+                         "should only display one filter");
+             strictEqual(_.str.trim($fs.children().text()),
+                         "filter 0",
+                         "should only display filter 0");
+             var done = $.Deferred();
+             view.complete_global_search({term: 'filter'}, function (comps) {
+                 done.resolve();
+                 strictEqual(comps.length, 1, "should only complete visible filter");
+                 strictEqual(comps[0].label, "Filter on: filter 0",
+                             "should complete filter 0");
+             });
+             return done;
+         });
+     });
++    test('invisible-previous-sibling', {asserts: 3}, function (instance, $fix, mock) {
++        var view = makeView(instance, mock, {}, [
++            '<search>',
++                '<filter string="filter 0" context="{&quot;test&quot;: 0}"/>',
++                '<filter string="filter 1" modifiers="{&quot;invisible&quot;: true}" context="{&quot;test&quot;: 1}"/>',
++                '<filter string="filter 2" modifiers="{&quot;invisible&quot;: true}" context="{&quot;test&quot;: 2}"/>',
++                '<filter string="filter 3" context="{&quot;test&quot;: 3}"/>',
++            '</search>'].join(''));
++        return view.appendTo($fix)
++        .done(function () {
++            // Select filter 3
++            $fix.find('.oe_searchview_filters ul li:contains("filter 3")').click();
++            equal(view.query.length, 1, "should have selected a filter");
++            var facet = view.query.at(0);
++            strictEqual(facet.values.at(0).get('label'), "filter 3",
++                        "should have correctly labelled the facet");
++            deepEqual(view.build_search_data().contexts, [{test: 3}],
++                      "should have built correct context");
++        });
++    });
+     // Invisible filter groups should not appear in the drawer
+     // Group invisibility should be inherited by children
+     test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) {
+         registerTestField(instance, {
+             complete: function () {
+                 return $.when([{label: this.attrs.string}]);
+             },
+         });
+         var view = makeView(instance, mock, {
+             field0: {type: 'test', string: 'Field 0'},
+             field1: {type: 'test', string: 'Field 1'},
+         }, [
+             '<search>',
+                 '<group string="Visibles">',
+                     '<field name="field0"/>',
+                     '<filter string="Filter 0"/>',
+                 '</group>',
+                 '<group string="Invisibles" modifiers="{&quot;invisible&quot;: true}">',
+                     '<field name="field1"/>',
+                     '<filter string="Filter 1"/>',
+                 '</group>',
+             '</search>'
+         ].join(''));
+         return view.appendTo($fix)
+         .then(function () {
+             strictEqual($fix.find('.oe_searchview_filters h3').length,
+                         1,
+                         "should only display one group");
+             strictEqual($fix.find('.oe_searchview_filters h3').text(),
+                         'w Visibles',
+                         "should only display the Visibles group (and its icon char)");
+             var $fs = $fix.find('.oe_searchview_filters ul');
+             strictEqual($fs.children().length, 1,
+                         "should only have one filter in the drawer");
+             strictEqual(_.str.trim($fs.text()), "Filter 0",
+                         "should have filter 0 as sole filter");
+             var done = $.Deferred();
+             view.complete_global_search({term: 'filter'}, function (compls) {
+                 done.resolve();
+                 strictEqual(compls.length, 2,
+                             "should have 2 completions");
+                 deepEqual(_.pluck(compls, 'label'),
+                           ['Field 0', 'Filter on: Filter 0'],
+                           "should complete on field 0 and filter 0");
+             });
+             return done;
+         });
+     });
+     // Default on invisible fields should still work, for fields and filters both
+     test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) {
+         var view = makeView(instance, mock, {
+             field: {type: 'char', string: "Field"},
+             field2: {type: 'char', string: "Field 2"},
+         }, [
+             '<search>',
+                 '<field name="field2"/>',
+                 '<filter name="filter2" string="Filter"',
+                        ' domain="[[\'qwa\', \'=\', 42]]"/>',
+                 '<group string="Invisibles" modifiers="{&quot;invisible&quot;: true}">',
+                     '<field name="field"/>',
+                     '<filter name="filter" string="Filter"',
+                            ' domain="[[\'whee\', \'=\', \'42\']]"/>',
+                 '</group>',
+             '</search>'
+         ].join(''), {field: "foo", filter: true});
+         return view.appendTo($fix)
+         .then(function () {
+             deepEqual(view.build_search_data(), {
+                 errors: [],
+                 groupbys: [],
+                 contexts: [],
+                 domains: [
+                     // Generated from field
+                     [['field', 'ilike', 'foo']],
+                     // generated from filter
+                     "[['whee', '=', '42']]"
+                 ],
+             }, "should yield invisible fields selected by defaults");
+         });
+     });
+ });
@@@ -265,6 -265,6 +265,12 @@@ instance.web_calendar.CalendarView = in
          //To parse Events we have to convert date Format
          var res_events = [],
              sidebar_items = {};
++        var selection_label = {};
++        if(this.fields[this.color_field].selection) {
++            _(this.fields[this.color_field].selection).each(function(value){
++                selection_label[value[0]] = value[1];
++            });
++        }
          for (var e = 0; e < events.length; e++) {
              var evt = events[e];
              if (!evt[this.date_start]) {
              if (this.color_field) {
                  var filter = evt[this.color_field];
                  if (filter) {
++                    if(this.fields[this.color_field].selection) {
++                        filter = selection_label[filter];
++                    }
                      var filter_value = (typeof filter === 'object') ? filter[0] : filter;
                      if (typeof(fn_filter) === 'function' && !fn_filter(filter_value)) {
                          continue;
      },
      get_event_data: function(event_obj) {
          var data = {
--            name: event_obj.text
++            name: event_obj.text || scheduler.locale.labels.new_event
          };
--        data[this.date_start] = instance.web.datetime_to_str(event_obj.start_date);
++        if (this.fields[this.date_start].type == 'date') {
++            data[this.date_start] = instance.web.date_to_str(event_obj.start_date)
++        }else {
++            data[this.date_start] = instance.web.datetime_to_str(event_obj.start_date)
++        }
          if (this.date_stop) {
              data[this.date_stop] = instance.web.datetime_to_str(event_obj.end_date);
          }
@@@ -1,6 -1,6 +1,6 @@@
  import openerp
  
--class DiagramView(openerp.addons.web.controllers.main.View):
++class DiagramView(openerp.addons.web.http.Controller):
      _cp_path = "/web_diagram/diagram"
  
      @openerp.addons.web.http.jsonrequest
@@@ -1,18 -1,46 +1,41 @@@
 -@charset "utf-8";
  .openerp .oe_kanban_view {
    background: white;
--  height: inherit;
--}
++  height: inherit; }
 +.openerp .oe_kanban_view .oe_kanban_column_higlight {
-   background: #eeddf6 !important;
- }
++  background: #eeddf6 !important; }
+ .openerp .oe_kanban_view .oe_view_nocontent {
+   position: relative;
+   z-index: 1;
+   max-width: none;
 -  height: 100%;
 -}
++  height: 100%; }
+ .openerp .oe_kanban_view .oe_view_nocontent .oe_view_nocontent_content {
+   margin-left: 90px;
+   margin-top: 5px;
 -  max-width: 700px;
 -}
++  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;
 -}
++  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%;
--}
++  width: 100%; }
+ .openerp .oe_kanban_view .oe_kanban_group_length {
+   text-align: center;
 -  display: none;
 -}
++  display: none; }
+ .openerp .oe_kanban_view .oe_kanban_group_length .oe_tag {
+   position: relative;
+   top: 8px;
 -  font-weight: bold;
 -}
++  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;
--}
++  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);
    -ms-transform: rotate(3deg);
    -webkit-transition: -webkit-transform 100ms linear;
    -moz-transition: -moz-transform 100ms linear;
--  transition: transform 100ms linear;
--}
++  transition: transform 100ms linear; }
  .openerp .oe_kanban_view .oe_kanban_left {
--  float: left;
--}
++  float: left; }
  .openerp .oe_kanban_view .oe_kanban_right {
--  float: right;
--}
++  float: right; }
  .openerp .oe_kanban_view .oe_kanban_clear {
--  clear: both;
--}
++  clear: both; }
  .openerp .oe_kanban_view .oe_kanban_content {
--  word-wrap: break-word;
--}
++  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;
--}
++  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;
--}
++  text-decoration: none; }
  .openerp .oe_kanban_view .oe_kanban_content .oe_star_on {
--  color: gold;
--}
++  color: gold; }
  .openerp .oe_kanban_view .oe_kanban_content div:first-child {
--  margin-right: 16px;
--}
++  margin-right: 16px; }
  .openerp .oe_kanban_view .oe_kanban_button_new {
    color: white;
--  background: #dc5f59;
--}
++  background: #dc5f59; }
  .openerp .oe_kanban_view .oe_kanban_groups {
--  height: inherit;
--}
++  height: inherit; }
  .openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_groups {
--  width: 100%;
--}
++  width: 100%; }
  .openerp .oe_kanban_view.oe_kanban_grouped_by_m2o .oe_kanban_group_title {
--  cursor: move;
--}
++  cursor: move; }
  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban {
--  float: right;
--}
++  float: right; }
  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban > span {
--  visibility: hidden;
--}
++  visibility: hidden; }
  .openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban > span {
--  visibility: visible;
--}
- .openerp .oe_kanban_view .oe_kanban_header:hover .oe_kanban_group_length {
-   display: none;
- }
++  visibility: visible; }
  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_menu {
    font-weight: normal;
--  font-size: 13px;
--}
++  font-size: 13px; }
  .openerp .oe_kanban_view .oe_kanban_group_title {
    position: relative;
    font-size: 16px;
    color: #333333;
    text-shadow: 0 1px 0 white;
    margin-right: 30px;
--  width: 200px;
--}
++  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_kanban_group_title .oe_kanban_group_length {
++  text-overflow: ellipsis; }
+ .openerp .oe_kanban_view .oe_fold_column .oe_kanban_group_length {
    position: absolute;
    top: -1px;
    right: -14px;
-   text-align: center;
    float: right;
- }
 -  display: block;
 -}
++  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;
--}
++  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;
--}
++  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;
--}
++  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;
--}
++  list-style-type: none; }
  .openerp .oe_kanban_view .oe_kanban_group_header.oe_kanban_no_group {
--  padding: 0px;
--}
++  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;
--}
++  border-right: 1px solid #b9b9b9; }
  .openerp .oe_kanban_view .oe_form .oe_kanban_column {
    padding: 0px;
--  background: white;
--}
++  background: white; }
  .openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_column_cards {
--  height: 100%;
--}
++  height: 100%; }
  .openerp .oe_kanban_view .oe_kanban_aggregates {
    padding: 0;
--  margin: 0px;
--}
- .openerp .oe_kanban_view .oe_kanban_column .oe_kanban_folded_column_cards {
-   visibility: hidden;
- }
++  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 {
-   display: block;
- }
++  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;
 -}
++  display: block; }
  .openerp .oe_kanban_view .oe_kanban_group_folded .oe_dropdown_kanban {
--  left: -5px;
--}
++  left: -5px; }
 +.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_folded_column_cards {
 +  visibility: visible;
 +  display: block !important;
-   height: 100%;
- }
++  height: 100%; }
  .openerp .oe_kanban_view .oe_kanban_group_title_undefined {
--  color: #666666;
--}
++  color: #666666; }
  .openerp .oe_kanban_view .oe_kanban_group_title_vertical {
    writing-mode: tb-rl;
    -webkit-transform: rotate(90deg);
    display: none;
    position: relative;
    opacity: 0.75;
--  top: 26px;
--}
++  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;
--}
++  position: relative; }
  .openerp .oe_kanban_view .oe_kanban_add {
    top: -8px;
- }
 -  z-index: 2;
 -}
++  z-index: 2; }
  .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
    top: -2px;
- }
- .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle .oe_kanban_group_length {
-   margin: 7px 1px -15px 2px;
-   font-weight: bold;
- }
 -  height: 14px;
 -}
++  height: 14px; }
  .openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_dropdown_toggle {
    cursor: pointer;
--  display: inline-block;
--}
++  display: inline-block; }
  .openerp .oe_kanban_view .oe_kanban_add {
--  float: right;
--}
++  float: right; }
  .openerp .oe_kanban_view .oe_kanban_quick_create_buttons {
--  margin: 4px 0;
--}
++  margin: 4px 0; }
  .openerp .oe_kanban_view .oe_kanban_no_group .oe_kanban_quick_create {
    width: 185px;
--  padding: 10px;
--}
++  padding: 10px; }
+ .openerp .oe_kanban_view .oe_kanban_quick_create {
 -  z-index: 2;
 -}
++  z-index: 2; }
  .openerp .oe_kanban_view .oe_kanban_quick_create input {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    width: 100%;
    -moz-box-shadow: none;
    -webkit-box-shadow: none;
--  -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;
--}
++  -box-shadow: 0px 0px 7px rgba(0, 133, 255, 0.3) inset; }
  .openerp .oe_kanban_view .oe_kanban_vignette {
    padding: 8px;
--  min-height: 100px;
--}
++  min-height: 100px; }
  .openerp .oe_kanban_view .oe_kanban_image {
    display: inline-block;
    vertical-align: top;
    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);
--}
++  -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;
--}
++  color: #4c4c4c; }
  .openerp .oe_kanban_view .oe_kanban_details h4 {
--  margin: 0 0 4px 0;
--}
++  margin: 0 0 4px 0; }
  .openerp .oe_kanban_view .oe_kanban_details .oe_tag {
    display: inline-block;
--  margin: 0 2px 2px 0;
--}
++  margin: 0 2px 2px 0; }
  .openerp .oe_kanban_view .oe_kanban_record {
    position: relative;
    display: block;
    margin: 0;
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
--  border-radius: 4px;
--}
++  border-radius: 4px; }
  .openerp .oe_kanban_view .oe_kanban_record:last-child {
--  margin-bottom: 0;
--}
++  margin-bottom: 0; }
  .openerp .oe_kanban_view .oe_kanban_record .oe_kanban_title {
    font-weight: bold;
--  margin: 2px 4px;
--}
++  margin: 2px 4px; }
+ .openerp .oe_kanban_view .oe_kanban_record .oe_kanban_alias {
 -  margin: 0px 0 8px 0;
 -}
++  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;
 -}
++  float: left; }
  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_record {
--  margin-bottom: 4px;
--}
++  margin-bottom: 4px; }
  .openerp .oe_kanban_view .oe_kanban_avatar_smallbox {
    height: 40px;
    width: 40px;
    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);
--}
++  -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;
--}
++  margin-bottom: 5px; }
  .openerp .oe_kanban_view .oe_kanban_box_header {
--  border-bottom: 1px solid #cccccc;
--}
++  border-bottom: 1px solid #cccccc; }
  .openerp .oe_kanban_view .oe_kanban_title {
    font-size: 95%;
    font-weight: bold;
--  padding: 0 4px 0 4px;
--}
++  padding: 0 4px 0 4px; }
  .openerp .oe_kanban_view .oe_kanban_small {
    font-size: 80%;
--  font-weight: normal;
--}
++  font-weight: normal; }
  .openerp .oe_kanban_view .oe_kanban_show_more {
    clear: both;
--  text-align: center;
--}
++  text-align: center; }
  .openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_show_more .oe_button {
--  width: 100%;
--}
++  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;
--}
++  -webkit-box-sizing: border-box; }
  .openerp .oe_kanban_view .oe_kanban_action_button {
    height: 22px;
--  margin: 0;
--}
++  margin: 0; }
  .openerp .oe_kanban_view .oe_kanban_action_a {
--  text-decoration: none;
--}
++  text-decoration: none; }
  .openerp .oe_kanban_view .oe_kanban_action_a:hover {
--  text-decoration: none;
--}
++  text-decoration: none; }
  .openerp .oe_kanban_view .oe_kanban_table {
    width: 100%;
    border: none;
    border-collapse: collapse;
    margin: 0;
--  padding: 0;
--}
++  padding: 0; }
  .openerp .oe_kanban_view .oe_kanban_table tr td {
--  padding: 0;
--}
++  padding: 0; }
  .openerp .oe_kanban_view .oe_kanban_table tr td.oe_kanban_title {
--  padding: 2px;
--}
++  padding: 2px; }
  .openerp .oe_kanban_view .oe_kanban_box_content {
    padding: 4px;
--  font-size: 90%;
--}
++  font-size: 90%; }
  .openerp .oe_kanban_view .oe_kanban_button {
    border: 1px solid #8ec1da;
    background-color: #ddeef6;
    text-shadow: 0 1px white;
    padding: 0 4px;
    font-size: 85%;
--  margin: 1px;
--}
++  margin: 1px; }
  .openerp .oe_kanban_view a.oe_kanban_button:hover, .openerp .oe_kanban_view .openerp button.oe_kanban_button:hover {
--  background-color: #eeddf6;
--}
++  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;
--}
++  clear: both; }
  .openerp .oe_kanban_view .oe_kanban_buttons_set a {
--  padding: 2px;
--}
++  padding: 2px; }
  .openerp .oe_kanban_view .oe_kanban_box_show_onclick {
--  display: none;
--}
++  display: none; }
  .openerp .oe_kanban_view .oe_kanban_draghandle {
--  cursor: move;
--}
++  cursor: move; }
  .openerp .oe_kanban_view .oe_kanban_color_border {
--  border-color: #cccccc;
--}
++  border-color: #cccccc; }
  .openerp .oe_kanban_view .oe_kanban_color_border {
--  border-color: #cccccc;
--}
++  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;
--}
++  list-style: circle; }
  .openerp .oe_kanban_view .oe_kanban_highlight {
    border-radius: 2px;
    -moz-border-radius: 2px;
    margin: 1px 4px;
    white-space: nowrap;
    display: inline-block;
--  line-height: 1em;
--}
++  line-height: 1em; }
  .openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_kanban_quick_create {
    margin-bottom: 4px;
    position: relative;
    -webkit-transition: -webkit-transform, -webkit-box-shadow, border 200ms linear;
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
--  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;
--}
++  -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;
--}
++  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;
--}
++  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;
--}
++  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;
--}
++  float: left; }
  .openerp .oe_kanban_view .oe_kanban_star {
    float: left;
    position: inline-block;
--  margin: 0 4px 0 0;
--}
++  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);
--}
++  -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
  .openerp .oe_kanban_view .oe_kanban_footer_left {
--  margin-top: 2px;
--}
++  margin-top: 2px; }
  .openerp .oe_kanban_view .oe_kanban_footer_left > span {
    margin-top: 2px;
    display: inline-block;
    line-height: 16px;
    -moz-border-radius: 3px;
    -webkit-border-radius: 3px;
--  border-radius: 3px;
--}
++  border-radius: 3px; }
  .openerp .oe_kanban_view .oe_kanban_footer_left > span .oe_e {
    line-height: 12px;
--  font-size: 22px;
--}
++  font-size: 22px; }
  .openerp .oe_kanban_view .oe_kanban_footer_left .oe_tags {
--  margin-right: 0;
--}
++  margin-right: 0; }
  .openerp .oe_kanban_view .oe_kanban_footer_left .oe_tags .oe_tag {
    display: inline-block;
    padding: 0 2px;
--  line-height: 14px;
--}
++  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;
--}
++  top: -1px; }
  .openerp .oe_kanban_view .oe_kanban_bottom_right {
    float: right;
    position: relative;
--  top: 2px;
--}
++  top: 2px; }
  .openerp .oe_kanban_view .oe_kanban_status {
    position: relative;
    top: 4px;
    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%);
--}
++  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%);
--}
++  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%);
--}
++  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;
--}
++  border-radius: 4px; }
  .openerp .oe_kanban_view .oe_kanban_ellipsis {
    overflow: hidden;
    text-overflow: ellipsis;
--  white-space: nowrap;
--}
++  white-space: nowrap; }
  .openerp .oe_kanban_view .oe_dropdown_kanban {
    float: right;
    cursor: pointer;
--  margin-top: -6px;
--}
++  margin-top: -6px; }
  .openerp .oe_kanban_view .oe_dropdown_kanban:hover {
--  text-decoration: none;
--}
++  text-decoration: none; }
  .openerp .oe_kanban_view .oe_dropdown_kanban .oe_dropdown_menu {
    left: 0;
    top: 28px;
    min-width: 160px;
--  padding: 2px;
--}
++  padding: 2px; }
  .openerp .oe_kanban_view .oe_dropdown_kanban .oe_dropdown_menu > li {
--  padding: 3px;
--}
++  padding: 3px; }
  .openerp .oe_kanban_view .oe_dropdown_kanban.oe_opened > span {
--  visibility: visible;
--}
++  visibility: visible; }
  .openerp .oe_kanban_view .oe_dropdown_kanban > span {
--  visibility: hidden;
--}
++  visibility: hidden; }
  .openerp .oe_kanban_view .oe_kanban_colorpicker {
--  white-space: nowrap;
--}
++  white-space: nowrap; }
  .openerp .oe_kanban_view .oe_kanban_colorpicker li {
    float: left;
    margin: 0;
--  padding: 0;
--}
++  padding: 0; }
  .openerp .oe_kanban_view .oe_kanban_colorpicker li a {
    display: inline-block;
    width: 16px;
    height: 16px;
--  border: 1px solid white;
--}
++  border: 1px solid white; }
  .openerp .oe_kanban_view .oe_kanban_colorpicker li a:hover {
--  border: 1px solid gray !important;
--}
++  border: 1px solid gray !important; }
  .openerp .oe_kanban_view .oe_kanban_colorpicker li:first-child a {
--  border: 1px solid #cccccc;
--}
++  border: 1px solid #cccccc; }
  .openerp .oe_kanban_view .oe_kanban_color_0 {
    background-color: white;
--  color: #5a5a5a;
--}
++  color: #5a5a5a; }
  .openerp .oe_kanban_view .oe_kanban_color_1 {
    background-color: #cccccc;
--  color: #424242;
--}
++  color: #424242; }
  .openerp .oe_kanban_view .oe_kanban_color_2 {
    background-color: #ffc7c7;
--  color: #7a3737;
--}
++  color: #7a3737; }
  .openerp .oe_kanban_view .oe_kanban_color_3 {
    background-color: #fff1c7;
--  color: #756832;
--}
++  color: #756832; }
  .openerp .oe_kanban_view .oe_kanban_color_4 {
    background-color: #e3ffc7;
--  color: #5d6937;
--}
++  color: #5d6937; }
  .openerp .oe_kanban_view .oe_kanban_color_5 {
    background-color: #c7ffd5;
--  color: #1a7759;
--}
++  color: #1a7759; }
  .openerp .oe_kanban_view .oe_kanban_color_6 {
    background-color: #c7ffff;
--  color: #1a5d83;
--}
++  color: #1a5d83; }
  .openerp .oe_kanban_view .oe_kanban_color_7 {
    background-color: #c7d5ff;
--  color: #3b3e75;
--}
++  color: #3b3e75; }
  .openerp .oe_kanban_view .oe_kanban_color_8 {
    background-color: #e3c7ff;
--  color: #4c3668;
--}
++  color: #4c3668; }
  .openerp .oe_kanban_view .oe_kanban_color_9 {
    background-color: #ffc7f1;
--  color: #6d2c70;
--}
++  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;
--}
++  top: -5px !important; }
  .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_column_higlight
 +        background: #eeddf6 !important
-     &.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
+         .oe_tag
+             position: relative
+             top: +8px
+             font-weight: bold
++    .oe_kanban_header:hover .oe_kanban_group_length
++        display: none
      .ui-sortable-placeholder
          border: 1px solid rgba(0,0,0,0.1)
          visibility: visible !important
  
      .oe_kanban_column, .oe_kanban_column_cards
          height: 100%
++
      .oe_kanban_aggregates
          padding: 0
          margin: 0px
              .oe_kanban_project_times
                  li
                      float: left
--       
++
      .oe_kanban_star
          float: left
          position: inline-block
@@@ -15,7 -15,7 +15,7 @@@ instance.web.ViewManagerAction.include(
                  }
                  evt.currentTarget.selectedIndex = 0;
          }else{
--            return this._super.apply(this,arguments); 
++            return this._super.apply(this,arguments);
          }
      }
  });
@@@ -232,10 -232,10 +232,11 @@@ instance.web_view_editor.ViewEditor 
          return main_object;
      },
      parse_xml: function(arch, view_id) {
++        //First element of att_list must be element tagname.
          main_object = {
              'level': 0,
              'id': this.xml_element_id +=1,
--            'att_list': [],
++            'att_list': ["view"],
              'name': _.str.sprintf("<view view_id = %s>", view_id),
              'child_id': []
          };
          var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null);
          parent_tr = self.get_object_by_id(parseInt($(parent_tr).attr('id').replace(/[^0-9]+/g, '')), this.one_object['main_object'], [])[0].att_list[0];
          _.each([tr, parent_tr],function(element) {
--            var value = _.has(_CHILDREN, element) ? element : _.str.include(html_tag, element)?"html_tag":false; 
++            var value = _.has(_CHILDREN, element) ? element : _.str.include(html_tag, element)?"html_tag":false;
              property_to_check.push(value);
          });
          field_dataset.call( 'fields_get', []).done(function(result) {
              var fields = _.keys(result);
              fields.push(" "),fields.sort();
--            self.on_add_node(property_to_check, fields);
++            self.on_add_node(property_to_check, fields, self.inject_position(parent_tr,tr));
          });
      },
++    inject_position : function(parent_tag,current_tag){
++        if(parent_tag == "view")
++            return ['Inside'];
++        if(current_tag == "field")
++            return ['After','Before'];
++        return ['After','Before','Inside'];
++    },
      do_node_edit: function(side) {
          var self = this;
          var result = self.get_object_by_id(this.one_object.clicked_tr_id, this.one_object['main_object'], []);
              var children = _.filter(xml_arch.childNodes[0].childNodes, function (child) {
                  return child.nodeType == 1;
              });
--            arch.arch = _.detect(children, function(xml_child) {
++            var inherited_view = _.detect(children, function(xml_child) {
                  var temp_obj = self.create_View_Node(xml_child),
                      insert = _.intersection(_.flatten(temp_obj.att_list),_.uniq(check_list));
                  if (insert.length == _.uniq(check_list).length ) {return xml_child;}
              });
--            xml_arch = QWeb.load_xml(arch.arch);
++            xml_arch = QWeb.load_xml(instance.web.xml_to_str(inherited_view));
          }
          return self.do_save_xml(xml_arch.documentElement, obj[0].child_id[0],obj[0].child_id, move_direct, update_values,arch);
      },
              });
          return def.promise();
      },
--    on_add_node: function(properties, fields){
++    on_add_node: function(properties, fields, position){
          var self = this;
          var  render_list = [{'name': 'node_type','selection': _.keys(_CHILDREN).sort(), 'value': 'field', 'string': 'Node Type','type': 'selection'},
                              {'name': 'field_value','selection': fields, 'value': false, 'string': '','type': 'selection'},
--                            {'name': 'position','selection': ['After','Before','Inside'], 'value': false, 'string': 'Position','type': 'selection'}];
++                            {'name': 'position','selection': position, 'value': false, 'string': 'Position','type': 'selection'}];
          this.add_widget = [];
          this.add_node_dialog = new instance.web.Dialog(this,{
              title: _t("Properties"),
@@@ -1186,7 -1186,7 +1194,7 @@@ var _CHILDREN = 
  //e.g.:xyz 'td' : ['field']
  };
  // Generic html_tag list and can be added html tag in future. It's support above _CHILDREN dict's *html_tag* by default.
--// For specific child node one has to define tag above and specify children tag in list. Like above xyz example. 
++// For specific child node one has to define tag above and specify children tag in list. Like above xyz example.
  var html_tag = ['div','h1','h2','h3','h4','h5','h6','td','tr'];
  
  var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',