[MERGE] forward port of branch 8.0 up to ed92589
authorChristophe Simonis <chs@odoo.com>
Thu, 23 Oct 2014 15:14:35 +0000 (17:14 +0200)
committerChristophe Simonis <chs@odoo.com>
Thu, 23 Oct 2014 15:14:35 +0000 (17:14 +0200)
20 files changed:
1  2 
addons/account/account_invoice_view.xml
addons/calendar/calendar.py
addons/mail/static/src/js/mail.js
addons/product/product.py
addons/product/product_view.xml
addons/project/report/project_report.py
addons/web/static/src/js/core.js
addons/web_graph/static/src/js/graph_widget.js
addons/website/models/website.py
addons/website/views/website_templates.xml
addons/website_forum/controllers/main.py
addons/website_forum/data/forum_data.xml
addons/website_forum/models/forum.py
addons/website_forum/static/src/js/website_forum.js
addons/website_forum/views/forum.xml
addons/website_forum/views/website_forum.xml
addons/website_mail/views/website_email_designer.xml
addons/website_sale/controllers/main.py
addons/website_sale/views/templates.xml
openerp/models.py

                      <group expand="0" string="Group By">
                        <filter name="group_by_partner_id" string="Partner" context="{'group_by':'partner_id'}"/>
                          <filter string="Salesperson" context="{'group_by':'user_id'}"/>
-                         <filter string="Category of Product" name="category_product" context="{'group_by':'categ_id','residual_invisible':True}"/>
                          <filter string="Status" context="{'group_by':'state'}"/>
                          <separator/>
                          <filter string="Period" context="{'group_by':'period_id'}"/>
                </p>
              </field>
          </record>
 -        <menuitem action="action_invoice_tree2" id="menu_action_invoice_tree2" parent="menu_finance_payables"/>
 +        <menuitem action="action_invoice_tree2" id="menu_action_invoice_tree2" parent="menu_finance_payables" sequence="1"/>
  
          <record id="action_invoice_tree3" model="ir.actions.act_window">
              <field name="name">Customer Refunds</field>
                </p>
              </field>
          </record>
 -        <menuitem action="action_invoice_tree4" id="menu_action_invoice_tree4" parent="menu_finance_payables"/>
 +        <menuitem action="action_invoice_tree4" id="menu_action_invoice_tree4" parent="menu_finance_payables" sequence="2"/>
  
          <act_window
             id="act_account_journal_2_account_invoice_opened"
@@@ -900,7 -900,7 +900,7 @@@ class calendar_event(osv.Model)
          'stop_datetime': fields.datetime('End Datetime', states={'done': [('readonly', True)]}, track_visibility='onchange'),  # old date_deadline
          'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
          'description': fields.text('Description', states={'done': [('readonly', True)]}),
 -        'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
 +        'class': fields.selection([('public', 'Everyone'), ('private', 'Only me'), ('confidential', 'Only internal users')], 'Privacy', states={'done': [('readonly', True)]}),
          'location': fields.char('Location', help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}),
          'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}),
  
          res = False
          new_id = False
  
-          # Special write of complex IDS
-         for event_id in ids:
+         # Special write of complex IDS
+         for event_id in list(ids):
              if len(str(event_id).split('-')) == 1:
                  continue
  
                  if data.get('rrule'):
                      new_id = self._detach_one_event(cr, uid, event_id, values, context=None)
  
-         res = super(calendar_event, self).write(cr, uid, ids, values, context=context)
+         res = super(calendar_event, self).write(cr, uid, [int(event_id) for event_id in ids], values, context=context)
  
          # set end_date for calendar searching
          if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \
@@@ -916,63 -916,12 +916,63 @@@ openerp.mail = function (session) 
              this.$('.oe_reply').on('click', this.on_message_reply);
              this.$('.oe_star').on('click', this.on_star);
              this.$('.oe_msg_vote').on('click', this.on_vote);
 +            this.$('.oe_mail_vote_count').on('mouseenter', this.on_hover);
              this.$('.oe_mail_expand').on('click', this.on_expand);
              this.$('.oe_mail_reduce').on('click', this.on_expand);
              this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
              this.$('.oe_mail_action_author').on('click', this.on_record_author_clicked);
          },
 -
 +        on_hover : function(event){
 +            var self = this;
 +            var voter = "";
 +            var limit = 10;
 +            event.stopPropagation();
 +            var $target = $(event.target).hasClass("fa-thumbs-o-up") ? $(event.target).parent() : $(event.target);
 +            //Note: We can set data-content attr on target element once we fetch data so that next time when one moves mouse on element it saves call
 +            //But if there is new like comes then we'll not have new likes in popover in that case
 +            if ($target.data('liker-list'))
 +            {
 +                voter = $target.data('liker-list');
 +                self.bindTooltipTo($target, voter);
 +                $target.tooltip('hide').tooltip('show');
 +                $(".tooltip").on("mouseleave", function () {
 +                    $(this).remove();
 +                });
 +            }else{
 +                this.ds_message.call('get_likers_list', [this.id, limit])
 +                .done(function (data) {
 +                    _.each(data, function(people, index) {
 +                        voter = voter + people.substring(0,1).toUpperCase() + people.substring(1);
 +                        if(index != data.length-1) {
 +                            voter = voter + "<br/>";
 +                        }
 +                    });
 +                    $target.data('liker-list', voter);
 +                    self.bindTooltipTo($target, voter);
 +                    $target.tooltip('hide').tooltip('show');
 +                    $(".tooltip").on("mouseleave", function () {
 +                        $(this).remove();
 +                    });
 +                });
 +            }
 +            return true;
 +        },
 +        bindTooltipTo: function($el, value) {
 +            $el.tooltip({
 +                'title': value,
 +                'placement': 'top',
 +                'container': this.el,
 +                'html': true,
 +                'trigger': 'manual',
 +                'animation': false
 +             }).on("mouseleave", function () {
 +                setTimeout(function () {
 +                    if (!$(".tooltip:hover").length) {
 +                        $el.tooltip("hide");
 +                    }
 +                },100);
 +            });
 +        },
          on_record_clicked: function  (event) {
              event.preventDefault();
              var self = this;
              this.$(".oe_msg_footer:first .oe_mail_vote_count").remove();
              this.$(".oe_msg_footer:first .oe_msg_vote").replaceWith(vote_element);
              this.$('.oe_msg_vote').on('click', this.on_vote);
 +            this.$('.oe_mail_vote_count').on('mouseenter', this.on_hover);
          },
  
          /**
          start: function () {
              this._super.apply(this, arguments);
              this.bind_events();
 +            return $.when();
          },
  
          /* instantiate the compose message object and insert this on the DOM.
          message_fetch: function (replace_domain, replace_context, ids, callback) {
              return this.ds_message.call('message_read', [
                      // ids force to read
-                     ids === false ? undefined : ids, 
+                     ids === false ? undefined : ids && ids.slice(0, this.options.fetch_limit),
                      // domain + additional
                      (replace_domain ? replace_domain : this.domain), 
                      // ids allready loaded
                      // context + additional
                      (replace_context ? replace_context : this.context), 
                      // parent_id
-                     this.context.default_parent_id || undefined
+                     this.context.default_parent_id || undefined,
+                     this.options.fetch_limit,
                  ]).done(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message')
                  ).done(this.proxy('message_fetch_set_read'));
          },
                  'compose_as_todo' : false,
                  'readonly' : false,
                  'emails_from_on_composer': true,
+                 'fetch_limit': 30   // limit of chatter messages
              }, this.action.params);
  
              this.action.params.help = this.action.help || false;
          start: function (options) {
              this._super.apply(this, arguments);
              this.message_render();
-             this.bind_events();
          },
          
          /**
  
          },
  
-         bind_events: function () {
-             $(document).scroll( _.bind(this.thread.on_scroll, this.thread) );
-             $(window).resize( _.bind(this.thread.on_scroll, this.thread) );
-             this.$el.resize( _.bind(this.thread.on_scroll, this.thread) );
-             window.setTimeout( _.bind(this.thread.on_scroll, this.thread), 500 );
-         },
      });
  
  
                  'show_compact_message': this.action.params.view_mailbox ? false : 1,
                  'view_inbox': false,
                  'emails_from_on_composer': false,
+                 'fetch_limit': 1000   // allow inbox to load all children messages
              }, this.action.params);
          },
  
           * @param {Object} defaults ??
           */
          load_searchview: function (defaults) {
 -            var ds_msg = new session.web.DataSetSearch(this, 'mail.message');
 -            this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, false);
 +            var self = this,
 +                ds_msg = new session.web.DataSetSearch(this, 'mail.message'),
 +                options = { $buttons: this.$('.oe-search-options') };
 +            this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, options);
              this.searchview.on('search_data', this, this.do_searchview_search);
 -            this.searchview.appendTo(this.$('.oe_view_manager_view_search'), 
 -                                   this.$('.oe_searchview_drawer_container'));
 +            this.searchview.appendTo(this.$('.oe-view-manager-search-view')).then(function () {
 +                self.searchview.toggle_visibility(true);
 +            });
              if (this.searchview.has_defaults) {
                  this.searchview.ready.then(this.searchview.do_search);
              }
@@@ -134,10 -134,10 +134,10 @@@ class product_uom(osv.osv)
          'name': fields.char('Unit of Measure', required=True, translate=True),
          'category_id': fields.many2one('product.uom.categ', 'Product Category', required=True, ondelete='cascade',
              help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."),
-         'factor': fields.float('Ratio', required=True,digits=(12, 12),
+         'factor': fields.float('Ratio', required=True, digits=0, # force NUMERIC with unlimited precision
              help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category:\n'\
                      '1 * (reference unit) = ratio * (this unit)'),
-         'factor_inv': fields.function(_factor_inv, digits=(12,12),
+         'factor_inv': fields.function(_factor_inv, digits=0, # force NUMERIC with unlimited precision
              fnct_inv=_factor_inv_write,
              string='Bigger Ratio',
              help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category:\n'\
@@@ -511,7 -511,8 +511,7 @@@ class product_template(osv.osv)
          'warranty': fields.float('Warranty'),
          'sale_ok': fields.boolean('Can be Sold', help="Specify if the product can be selected in a sales order line."),
          'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
 -        'state': fields.selection([('',''),
 -            ('draft', 'In Development'),
 +        'state': fields.selection([('draft', 'In Development'),
              ('sellable','Normal'),
              ('end','End of Lifecycle'),
              ('obsolete','Obsolete')], 'Status'),
          ''' Store the standard price change in order to be able to retrieve the cost of a product template for a given date'''
          if isinstance(ids, (int, long)):
              ids = [ids]
 -        if 'uom_po_id' in vals:
 -            new_uom = self.pool.get('product.uom').browse(cr, uid, vals['uom_po_id'], context=context)
 -            for product in self.browse(cr, uid, ids, context=context):
 -                old_uom = product.uom_po_id
 -                if old_uom.category_id.id != new_uom.category_id.id:
 -                    raise osv.except_osv(_('Unit of Measure categories Mismatch!'), _("New Unit of Measure '%s' must belong to same Unit of Measure category '%s' as of old Unit of Measure '%s'. If you need to change the unit of measure, you may deactivate this product from the 'Procurements' tab and create a new one.") % (new_uom.name, old_uom.category_id.name, old_uom.name,))
          if 'standard_price' in vals:
              for prod_template_id in ids:
                  self._set_standard_price(cr, uid, prod_template_id, vals['standard_price'], context=context)
@@@ -11,7 -11,7 +11,7 @@@
              <field name="arch" type="xml">
                  <search string="Product">
                      <field name="name" string="Product"/>
 -                    <filter string="Services" icon="terp-accessories-archiver" domain="[('type','=','service')]"/>
 +                    <filter string="Services" name="services" domain="[('type','=','service')]"/>
                      <filter string="Consumable" name="consumable" icon="terp-accessories-archiver" domain="[('type','=','consu')]" help="Consumable products"/>
                      <separator/>
                      <filter string="Can be Sold" name="filter_to_sell" icon="terp-accessories-archiver-minus" domain="[('sale_ok','=',1)]"/>
  
          <!-- product product -->
  
 -        <menuitem id="prod_config_main" name="Product Categories &amp; Attributes" parent="base.menu_base_config" sequence="70" groups="base.group_no_one"/>
 +        <menuitem id="prod_config_main" name="Products" parent="base.menu_base_config" sequence="2" groups="base.group_no_one"/>
  
          <record id="product_product_tree_view" model="ir.ui.view">
              <field name="name">product.product.tree</field>
  
          <menuitem action="attribute_action"
              id="menu_attribute_action"
 -            parent="product.prod_config_main" sequence="9" />
 +            parent="product.prod_config_main" sequence="4" />
  
          <record id="variants_tree_view" model="ir.ui.view">
              <field name="name">product.attribute.value.tree</field>
  
          <menuitem action="variants_action"
              id="menu_variants_action"
 -            parent="product.prod_config_main" sequence="10" />
 +            parent="product.prod_config_main" sequence="5" />
  
          <!--  -->
  
              <field name="model">product.category</field>
              <field name="arch" type="xml">
                  <form string="Product Categories">
 -                    <sheet>
 -                        <div class="oe_title">
 -                            <label for="name" class="oe_edit_only"/>
 -                            <h1>
 -                                <field name="name"/>
 -                            </h1>
 -                        </div>
 -                        <group>
 -                            <group name="parent" col="4">
 -                                <field name="parent_id"/>
 -                                <field name="type"/>
 -                            </group>
 +                    <div class="oe_title">
 +                        <label for="name" class="oe_edit_only"/>
 +                        <h1>
 +                            <field name="name"/>
 +                        </h1>
 +                    </div>
 +                    <group>
 +                        <group name="parent" col="4">
 +                            <field name="parent_id"/>
 +                            <field name="type"/>
                          </group>
 -                    </sheet>
 +                    </group>
                  </form>
              </field>
          </record>
              parent="base.menu_product"
              sequence="30" groups="base.group_no_one"/>
          <record id="product_category_action_form" model="ir.actions.act_window">
 -            <field name="name">Product Categories</field>
 +            <field name="name">Internal Categories</field>
              <field name="type">ir.actions.act_window</field>
              <field name="res_model">product.category</field>
              <field name="view_type">form</field>
                              <field name="uom_type" on_change="onchange_type(uom_type)"/>
                              <label for="factor"/>
                              <div>
-                                 <field name="factor" attrs="{'invisible':[('uom_type','!=','smaller')]}"/>
-                                 <field name="factor_inv" attrs="{'invisible':[('uom_type','!=','bigger')]}"/>
+                                 <field name="factor"
+                                     digits="[42,5]"
+                                     attrs="{'invisible':[('uom_type','!=','smaller')],
+                                             'readonly':[('uom_type','!=','smaller')]}"/>
+                                 <field name="factor_inv"
+                                     digits="[42,5]"
+                                     attrs="{'invisible':[('uom_type','!=','bigger')],
+                                             'readonly':[('uom_type','!=','bigger')]}"/>
                                  <p attrs="{'invisible':[('uom_type','!=','smaller')]}" class="oe_grey">
                                      e.g: 1 * (reference unit) = ratio * (this unit)
                                  </p>
              </field>
          </record>
          <menuitem id="next_id_16" name="Units of Measure" parent="prod_config_main" sequence="30" groups="product.group_uom"/>
 -        <menuitem action="product_uom_form_action" id="menu_product_uom_form_action" parent="base.menu_base_config" sequence="30" groups="product.group_uom"/>
 +        <menuitem action="product_uom_form_action" id="menu_product_uom_form_action" parent="product.prod_config_main" sequence="6" groups="product.group_uom"/>
  
          <record id="product_uom_categ_form_view" model="ir.ui.view">
              <field name="name">product.uom.categ.form</field>
                </p>
              </field>
          </record>
 -        <menuitem action="product_uom_categ_form_action" id="menu_product_uom_categ_form_action" parent="base.menu_base_config" sequence="25" groups="base.group_no_one"/>
 +        <menuitem action="product_uom_categ_form_action" id="menu_product_uom_categ_form_action" parent="product.prod_config_main" sequence="7" groups="base.group_no_one"/>
  
          <record id="product_ul_form_view" model="ir.ui.view">
              <field name="name">product.ul.form.view</field>
              </field>
          </record>
          <menuitem
 -            action="product_ul_form_action" groups="product.group_stock_packaging" id="menu_product_ul_form_action" parent="prod_config_main" sequence="5"/>
 +            action="product_ul_form_action" groups="product.group_stock_packaging" id="menu_product_ul_form_action" parent="prod_config_main" sequence="3"/>
  
          <record id="product_packaging_tree_view" model="ir.ui.view">
              <field name="name">product.packaging.tree.view</field>
@@@ -30,6 -30,7 +30,6 @@@ class report_project_task_user(osv.osv)
      _columns = {
          'name': fields.char('Task Summary', readonly=True),
          'user_id': fields.many2one('res.users', 'Assigned To', readonly=True),
 -        'reviewer_id': fields.many2one('res.users', 'Reviewer', readonly=True),
          'date_start': fields.datetime('Assignation Date', readonly=True),
          'no_of_days': fields.integer('# of Days', size=128, readonly=True),
          'date_end': fields.datetime('Ending Date', readonly=True),
@@@ -70,6 -71,7 +70,6 @@@
                      t.date_deadline as date_deadline,
                      abs((extract('epoch' from (t.write_date-t.date_start)))/(3600*24))  as no_of_days,
                      t.user_id,
 -                    t.reviewer_id,
                      progress as progress,
                      t.project_id,
                      t.effective_hours as hours_effective,
@@@ -85,7 -87,7 +85,7 @@@
                      planned_hours as hours_planned,
                      (extract('epoch' from (t.write_date-t.create_date)))/(3600*24)  as closing_days,
                      (extract('epoch' from (t.date_start-t.create_date)))/(3600*24)  as opening_days,
-                     (extract('epoch' from (t.date_deadline-now())))/(3600*24)  as delay_endings_days
+                     (extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24)  as delay_endings_days
                FROM project_task t
                  WHERE t.active = 'true'
                  GROUP BY
                      date_deadline,
                      date_last_stage_update,
                      t.user_id,
 -                    t.reviewer_id,
                      t.project_id,
                      t.priority,
                      name,
@@@ -311,8 -311,9 +311,8 @@@ instance.web.Session.include( /** @lend
              self.module_list = all_modules;
  
              var loaded = self.load_translations();
 -            var datejs_locale = "/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js";
 -
 -            var file_list = [ datejs_locale ];
 +            var locale = "/web/webclient/locale/" + self.user_context.lang || 'en_US';
 +            var file_list = [ locale ];
              if(to_load.length) {
                  loaded = $.when(
                      loaded,
@@@ -554,7 -555,7 +554,7 @@@ $.fn.getAttributes = function() 
      if (this.length) {
          for (var attr, i = 0, attrs = this[0].attributes, l = attrs.length; i < l; i++) {
              attr = attrs.item(i);
 -            o[attr.nodeName] = attr.nodeValue;
 +            o[attr.nodeName] = attr.value;
          }
      }
      return o;
@@@ -775,6 -776,7 +775,7 @@@ $.fn.tooltip.Constructor.DEFAULTS.trigg
  $.fn.tooltip.Constructor.DEFAULTS.container = 'body';
  //overwrite bootstrap tooltip method to prevent showing 2 tooltip at the same time
  var bootstrap_show_function = $.fn.tooltip.Constructor.prototype.show;
+ $.fn.modal.Constructor.prototype.enforceFocus = function () { };
  $.fn.tooltip.Constructor.prototype.show = function () {
      $('.tooltip').remove();
      //the following fix the bug when using placement
@@@ -20,12 -20,12 +20,12 @@@ openerp.web_graph.Graph = openerp.web.W
          this.model = model;
          this.domain = domain;
          this.mode = options.mode || 'pivot';  // pivot, bar, pie, line
 -        this.heatmap_mode = options.heatmap_mode || 'none';
          this.visible_ui = options.visible_ui || true;
          this.bar_ui = options.bar_ui || 'group';
          this.graph_view = options.graph_view || null;
          this.pivot_options = options;
          this.title = options.title || 'Data';
 +        this.$buttons = options.$buttons;
      },
  
      start: function() {
          this.table = $('<table>');
          this.$('.graph_main_content').append(this.table);
  
 +        this.$buttons.find('.oe-pivot-mode').click(function () {
 +            self.set_mode.bind(self)('pivot');
 +        });
 +        this.$measure_list = this.$buttons.find('.oe-measure-list');
 +
 +        this.$buttons.find('.oe-bar-mode').click(function () {
 +            self.set_mode.bind(self)('bar');
 +        });
 +        this.$buttons.find('.oe-line-mode').click(function () {
 +            self.set_mode.bind(self)('line');
 +        });
 +        this.$buttons.find('.oe-pie-mode').click(function () {
 +            self.set_mode.bind(self)('pie');
 +        });
 +        this.$buttons.find('.fa-expand').click(this.swap_axis.bind(this));
 +        this.$buttons.find('.fa-arrows-alt').click(function () {
 +            self.pivot.expand_all().then(self.proxy('display_data'));
 +        });
 +        this.$buttons.find('.fa-download').click(this.export_xls.bind(this));
 +
          var indexes = {'pivot': 0, 'bar': 1, 'line': 2, 'chart': 3};
 -        this.$('.graph_mode_selection label').eq(indexes[this.mode]).addClass('active');
 +        this.$('.graph_mode_selection label').eq(indexes[this.mode]).addClass('selected');
  
          if (this.mode !== 'pivot') {
              this.$('.graph_heatmap label').addClass('disabled');
@@@ -62,6 -42,7 +62,6 @@@
          } else {
              this.$('.graph_main_content').addClass('graph_pivot_mode');
          }
 -
          // get search view
          var parent = this.getParent();
          while (!(parent instanceof openerp.web.ViewManager)) {
  
          return this.model.call('fields_get', []).then(function (f) {
              self.fields = f;
-             self.fields.__count = {field:'__count', type: 'integer', string:_t('Quantity')};
+             self.fields.__count = {field:'__count', type: 'integer', string:_t('Count')};
              self.groupby_fields = self.get_groupby_fields();
              self.measure_list = self.get_measures();
              self.add_measures_to_options();
              self.pivot_options.row_groupby = self.create_field_values(self.pivot_options.row_groupby || []);
              self.pivot_options.col_groupby = self.create_field_values(self.pivot_options.col_groupby || []);
-             self.pivot_options.measures = self.create_field_values(self.pivot_options.measures || [{field:'__count', type: 'integer', string:'Quantity'}]);
+             self.pivot_options.measures = self.create_field_values(self.pivot_options.measures || [{field:'__count', type: 'integer', string:'Count'}]);
              self.pivot = new openerp.web_graph.PivotTable(self.model, self.domain, self.fields, self.pivot_options);
              self.pivot.update_data().then(function () {
                  self.display_data();
      // this method gets the fields that appear in the search view, under the 
      // 'Groupby' heading
      get_search_fields: function () {
 +        // this method is disabled for now.  This requires extensive changes because the
 +        // search view works quite differently:  But the graph view is going to be split 
 +        // soon in pivot view and graph view.  The pivot view will then properly handle 
 +        // groupbys.  
 +        return [];  
          var self = this;
  
          var groupbygroups = _(this.search_view.drawer.inputs).select(function (g) {
      },
  
      add_measures_to_options: function() {
 -        this.$('.graph_measure_selection').append(
 +        this.$measure_list.append(
          _.map(this.measure_list, function (measure) {
              return $('<li>').append($('<a>').attr('data-choice', measure.field)
                                       .attr('href', '#')
                                       .text(measure.string));
          }));
 +        this.$measure_list.find('li').click(this.measure_selection.bind(this));
      },
  
      // ----------------------------------------------------------------------
          this.display_data();
      },
  
 -    set_heatmap_mode: function (mode) { // none, row, col, all
 -        this.heatmap_mode = mode;
 -        if (mode === 'none') {
 -            this.$('.graph_heatmap label').removeClass('disabled');
 -            this.$('.graph_heatmap label').removeClass('active');
 -        }
 -        this.display_data();
 -    },
 -
      create_field_value: function (f) {
          var field = (_.contains(f, ':')) ? f.split(':')[0] : f,
              groupby_field = _.findWhere(this.groupby_fields, {field:field}),
  
      put_measure_checkmarks: function () {
          var self = this,
 -            measures_li = this.$('.graph_measure_selection a');
 -        measures_li.removeClass('oe_selected');
 +            measures_li = this.$measure_list.find('li');
 +        measures_li.removeClass('selected');
          _.each(this.measure_list, function (measure, index) {
              if (_.findWhere(self.pivot.measures, measure)) {
 -                measures_li.eq(index).addClass('oe_selected');
 +                measures_li.eq(index).addClass('selected');
              }
          });
  
          }
      },
  
 -    heatmap_mode_selection: function (event) {
 -        event.preventDefault();
 -        var mode = event.currentTarget.getAttribute('data-mode');
 -        if (this.heatmap_mode === mode) {
 -            event.stopPropagation();
 -            this.set_heatmap_mode('none');
 -        } else {
 -            this.set_heatmap_mode(mode);
 -        }
 -    },
 -
      header_cell_clicked: function (event) {
          event.preventDefault();
          event.stopPropagation();
          var formatted_value = raw && !_.isUndefined(value) ? value : openerp.web.format_value(value, {type:this.pivot.measures[index].type}),
              cell = {value:formatted_value};
  
 -        if (this.heatmap_mode === 'none') { return cell; }
 -        var total = (this.heatmap_mode === 'both') ? this.pivot.get_total()[index]
 -                  : (this.heatmap_mode === 'row')  ? this.pivot.get_total(row)[index]
 -                  : this.pivot.get_total(col)[index];
 -        var color = Math.floor(90 + 165*(total - Math.abs(value))/total);
 -        if (color < 255) {
 -            cell.color = color;
 -        }
          return cell;
      },
  
          this.$('.graph_main_content svg').remove();
          this.$('.graph_main_content div').remove();
          this.table.empty();
 -        this.table.toggleClass('heatmap', this.heatmap_mode !== 'none');
          this.$('.graph_options_selection label').last().toggleClass('disabled', this.pivot.no_data);
          this.width = this.$el.width();
          this.height = Math.min(Math.max(document.documentElement.clientHeight - 116 - 60, 250), Math.round(0.8*this.$el.width()));
@@@ -28,7 -28,6 +28,7 @@@ from openerp.osv import orm, osv, field
  from openerp.tools import html_escape as escape, ustr, image_resize_and_sharpen, image_save_for_web
  from openerp.tools.safe_eval import safe_eval
  from openerp.addons.web.http import request
 +from werkzeug.exceptions import NotFound
  
  logger = logging.getLogger(__name__)
  
@@@ -124,12 -123,6 +124,12 @@@ def slug(value)
  # NOTE: as the pattern is used as it for the ModelConverter (ir_http.py), do not use any flags
  _UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[A-Za-z0-9-_]+?\w)-)?(-?\d+)(?=$|/)')
  
 +DEFAULT_CDN_FILTERS = [
 +    "^/[^/]+/static/",
 +    "^/web/(css|js)/",
 +    "^/website/image/",
 +]
 +
  def unslug(s):
      """Extract slug and id from a string.
          Always return un 2-tuple (str|None, int|None)
@@@ -143,19 -136,20 +143,19 @@@ def urlplus(url, params)
      return werkzeug.Href(url)(params or None)
  
  class website(osv.osv):
 -    def _get_menu_website(self, cr, uid, ids, context=None):
 -        # IF a menu is changed, update all websites
 -        return self.search(cr, uid, [], context=context)
 -
      def _get_menu(self, cr, uid, ids, name, arg, context=None):
 -        root_domain = [('parent_id', '=', False)]
 -        menus = self.pool.get('website.menu').search(cr, uid, root_domain, order='id', context=context)
 -        menu = menus and menus[0] or False
 -        return dict( map(lambda x: (x, menu), ids) )
 +        res = {}
 +        menu_obj = self.pool.get('website.menu')
 +        for id in ids:
 +            menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False), ('website_id', '=', id)], order='id', context=context)
 +            res[id] = menu_ids and menu_ids[0] or False
 +        return res
  
      _name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
      _description = "Website"
      _columns = {
 -        'name': fields.char('Domain'),
 +        'name': fields.char('Website Name'),
 +        'domain': fields.char('Website Domain'),
          'company_id': fields.many2one('res.company', string="Company"),
          'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'),
          'default_lang_id': fields.many2one('res.lang', string="Default language"),
          'social_googleplus': fields.char('Google+ Account'),
          'google_analytics_key': fields.char('Google Analytics Key'),
          'user_id': fields.many2one('res.users', string='Public User'),
 +        'compress_html': fields.boolean('Compress HTML'),
 +        'cdn_activated': fields.boolean('Activate CDN for assets'),
 +        'cdn_url': fields.char('CDN Base URL'),
 +        'cdn_filters': fields.text('CDN Filters', help="URL matching those filters will be rewritten using the CDN Base URL"),
          'partner_id': fields.related('user_id','partner_id', type='many2one', relation='res.partner', string='Public Partner'),
 -        'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu',
 -            store= {
 -                'website.menu': (_get_menu_website, ['sequence','parent_id','website_id'], 10)
 -            })
 +        'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu')
      }
 -
      _defaults = {
 -        'company_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID, 'base.public_user'),
 +        'user_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID, 'base.public_user'),
 +        'company_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID,'base.main_company'),
 +        'compress_html': False,
 +        'cdn_activated': False,
 +        'cdn_url': '//localhost:8069/',
 +        'cdn_filters': '\n'.join(DEFAULT_CDN_FILTERS),
      }
  
      # cf. Wizard hack in website_views.xml
          except ValueError:
              # new page
              _, template_id = imd.get_object_reference(cr, uid, template_module, template_name)
 -            page_id = view.copy(cr, uid, template_id, context=context)
 +            website_id = context.get('website_id')
 +            key = template_module+'.'+page_name
 +            page_id = view.copy(cr, uid, template_id, {'website_id': website_id, 'key': key}, context=context)
              page = view.browse(cr, uid, page_id, context=context)
              page.write({
                  'arch': page.arch.replace(template, page_xmlid),
                  'name': page_name,
                  'page': ispage,
              })
 -            imd.create(cr, uid, {
 -                'name': page_name,
 -                'module': template_module,
 -                'model': 'ir.ui.view',
 -                'res_id': page_id,
 -                'noupdate': True
 -            }, context=context)
          return page_xmlid
  
      def page_for_name(self, cr, uid, ids, name, module='website', context=None):
          website = self.browse(cr, uid, id)
          return [(lg.code, lg.name) for lg in website.language_ids]
  
 +    def get_cdn_url(self, cr, uid, uri, context=None):
 +        # Currently only usable in a website_enable request context
 +        if request and request.website and not request.debug:
 +            cdn_url = request.website.cdn_url
 +            cdn_filters = (request.website.cdn_filters or '').splitlines()
 +            for flt in cdn_filters:
 +                if flt and re.match(flt, uri):
 +                    return urlparse.urljoin(cdn_url, uri)
 +        return uri
 +
      def get_languages(self, cr, uid, ids, context=None):
          return self._get_languages(cr, uid, ids[0], context=context)
  
                  lang['hreflang'] = lang['short']
          return langs
  
 +    @openerp.tools.ormcache(skiparg=4)
 +    def _get_current_website_id(self, cr, uid, domain_name, context=None):
 +        website_id = 1
 +        if request:
 +            ids = self.search(cr, uid, [('domain', '=', domain_name)], context=context)
 +            if ids:
 +                website_id = ids[0]
 +        return website_id
 +
      def get_current_website(self, cr, uid, context=None):
 -        # TODO: Select website, currently hard coded
 -        return self.pool['website'].browse(cr, uid, 1, context=context)
 +        domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
 +        website_id = self._get_current_website_id(cr, uid, domain_name, context=context)
 +        return self.browse(cr, uid, website_id, context=context)
  
      def is_publisher(self, cr, uid, ids, context=None):
          Access = self.pool['ir.model.access']
          return Access.check(cr, uid, 'ir.ui.menu', 'read', False, context=context)
  
      def get_template(self, cr, uid, ids, template, context=None):
 -        if isinstance(template, (int, long)):
 -            view_id = template
 -        else:
 -            if '.' not in template:
 -                template = 'website.%s' % template
 -            module, xmlid = template.split('.', 1)
 -            model, view_id = request.registry["ir.model.data"].get_object_reference(cr, uid, module, xmlid)
 -        return self.pool["ir.ui.view"].browse(cr, uid, view_id, context=context)
 +        if not isinstance(template, (int, long)) and '.' not in template:
 +            template = 'website.%s' % template
 +        View = self.pool['ir.ui.view']
 +        view_id = View.get_view_id(cr, uid, template, context=context)
 +        if not view_id:
 +            raise NotFound
 +        return View.browse(cr, uid, view_id, context=context)
  
      def _render(self, cr, uid, ids, template, values=None, context=None):
          # TODO: remove this. (just kept for backward api compatibility for saas-3)
@@@ -803,7 -778,7 +803,7 @@@ class res_partner(osv.osv)
              'zoom': zoom,
              'sensor': 'false',
          }
-         return urlplus('http://maps.googleapis.com/maps/api/staticmap' , params)
+         return urlplus('//maps.googleapis.com/maps/api/staticmap' , params)
  
      def google_map_link(self, cr, uid, ids, zoom=8, context=None):
          partner = self.browse(cr, uid, ids[0], context=context)
@@@ -6,6 -6,11 +6,6 @@@
  
  <!-- Layout and generic templates -->
  
 -<template id="website.theme" name="Theme">
 -    <link id="bootstrap_css" rel='stylesheet' href='/web/static/lib/bootstrap/css/bootstrap.css' t-ignore="true"/>
 -    <link rel="stylesheet" href='/website/static/src/css/website.css' t-ignore="true"/>
 -</template>
 -
  <template id="website.assets_frontend" name="Website assets">
      <t t-call="website.theme"/>
  
@@@ -15,7 -20,7 +15,7 @@@
  
      <script type="text/javascript" src="/website/static/src/js/website.snippets.animation.js"></script>
      <script type="text/javascript" src="/web/static/lib/bootstrap/js/bootstrap.js"></script>
 -
 +    
  </template>
  
  <template id="assets_backend" name="website assets for backend" inherit_id="web.assets_backend">
@@@ -67,7 -72,7 +67,7 @@@
                  <t t-set="additional_title" t-value="main_object.name"/>
              </t>
              <t t-if="not title">
 -                <t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t>
 +                <t t-set="title"><t t-if="additional_title"><t t-raw="additional_title"/> | </t><t t-esc="(website or res_company).name"/></t>
              </t>
              <meta name="viewport" content="initial-scale=1"/>
              <meta name="description" t-att-content="main_object and 'website_meta_description' in main_object
              <t t-call-assets="website.assets_frontend" t-js="false"/>
  
              <t t-raw="head or ''" name='layout_head'/>
-             <t t-if="website and website.google_analytics_key">
-                 <script>
-                     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-                     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-                     m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-                     })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-                     ga('create', _.str.trim('<t t-esc="website.google_analytics_key"/>'), 'auto');
-                     ga('send','pageview');
-                 </script>
-             </t>
          </head>
          <body>
              <div id="wrapwrap">
                                      <span class="icon-bar"></span>
                                      <span class="icon-bar"></span>
                                  </button>
 -                                <a class="navbar-brand" href="/" t-field="res_company.name"/>
 +                                <a class="navbar-brand" href="/">YourCompany</a>
                              </div>
                              <div class="collapse navbar-collapse navbar-top-collapse">
                                  <ul class="nav navbar-nav navbar-right" id="top_menu">
                                      <li class="dropdown" t-ignore="true" t-if="website.user_id != user_id">
                                          <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                                              <b>
 -                                                <span t-esc="user_id.name"/>
 +                                                <span t-esc="(len(user_id.name)&gt;25) and (user_id.name[:23]+'...') or user_id.name"/>
                                                  <span class="caret"></span>
                                              </b>
                                          </a>
                                          <ul class="dropdown-menu js_usermenu" role="menu">
 -                                            <li><a href="/web" role="menuitem">My Account</a></li>
 -                                            <li class="divider"/>
                                              <li><a t-attf-href="/web/session/logout?redirect=/" role="menuitem">Logout</a></li>
                                          </ul>
                                      </li>
  
              <t t-call-assets="web.assets_common" t-css="false"/>
              <t t-call-assets="website.assets_frontend" t-css="false"/>
+             <script t-if="website and website.google_analytics_key">
+                 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+                 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+                 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+                 })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+                 ga('create', _.str.trim('<t t-esc="website.google_analytics_key"/>'), 'auto');
+                 ga('send','pageview');
+             </script>
          </body>
      </html>
  </template>
      <script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
      <script type="text/javascript" src="/website/static/src/js/website.tour.banner.js"></script> <!-- groups="base.group_website_designer" -->
      <script type="text/javascript" src="/website/static/src/js/website.snippets.editor.js"></script>
 +    <script type="text/javascript" src="/website/static/src/js/website.snippets.gallery.js" />
      <script type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
      <script type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
 +    <script type="text/javascript" src="/website/static/src/js/website.theme.js"></script>
  
  </template>
  
  <template id="footer_custom" inherit_id="website.layout" name="Footer">
      <xpath expr="//div[@id='footer_container']" position="replace">
          <div class="oe_structure" id="footer">
 -            <section data-snippet-id='three-columns' class="mt16 mb16">
 +            <section class="mt16 mb16">
                  <div class="container">
                      <div class="row">
                          <div class="col-md-4">
@@@ -829,7 -832,7 +827,7 @@@ Sitemap: <t t-esc="url_root"/>sitemap.x
          <div id="wrap">
              <div class="oe_structure">
  
 -                <section data-snippet-id="title">
 +                <section>
                      <div class="container">
                          <div class="row">
                              <div class="col-md-12">
                      </div>
                  </section>
  
 -                <section data-snippet-id="text-image">
 +                <section>
                      <div class="container">
                          <div class="row">
                              <div class="col-md-6 mt32">
@@@ -1,12 -1,8 +1,10 @@@
  # -*- coding: utf-8 -*-
  
- from datetime import datetime
  import werkzeug.urls
  import werkzeug.wrappers
- import re
  import simplejson
 +import lxml
 +from urllib2 import urlopen
  
  from openerp import tools
  from openerp import SUPERUSER_ID
@@@ -15,7 -11,6 +13,6 @@@ from openerp.addons.web.controllers.mai
  from openerp.addons.web.http import request
  from openerp.addons.website.controllers.main import Website as controllers
  from openerp.addons.website.models.website import slug
- from openerp.tools.translate import _
  
  controllers = controllers()
  
@@@ -37,13 -32,15 +34,16 @@@ class WebsiteForum(http.Controller)
  
      def _prepare_forum_values(self, forum=None, **kwargs):
          user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, context=request.context)
-         values = {'user': user,
-                   'is_public_user': user.id == request.website.user_id.id,
-                   'notifications': self._get_notifications(),
-                   'header': kwargs.get('header', dict()),
-                   'searches': kwargs.get('searches', dict()),
-                   'no_introduction_message': request.httprequest.cookies.get('no_introduction_message', False),
-                   }
+         values = {
+             'user': user,
+             'is_public_user': user.id == request.website.user_id.id,
+             'notifications': self._get_notifications(),
+             'header': kwargs.get('header', dict()),
+             'searches': kwargs.get('searches', dict()),
++            'no_introduction_message': request.httprequest.cookies.get('no_introduction_message', False),
+             'validation_email_sent': request.session.get('validation_email_sent', False),
+             'validation_email_done': request.session.get('validation_email_done', False),
+         }
          if forum:
              values['forum'] = forum
          elif kwargs.get('forum_id'):
          values.update(kwargs)
          return values
  
+     # User and validation
+     # --------------------------------------------------
+     @http.route('/forum/send_validation_email', type='json', auth='user', website=True)
+     def send_validation_email(self, forum_id=None, **kwargs):
+         request.registry['res.users'].send_forum_validation_email(request.cr, request.uid, request.uid, forum_id=forum_id, context=request.context)
+         request.session['validation_email_sent'] = True
+         return True
+     @http.route('/forum/validate_email', type='http', auth='public', website=True)
+     def validate_email(self, token, id, email, forum_id=None, **kwargs):
+         if forum_id:
+             try:
+                 forum_id = int(forum_id)
+             except ValueError:
+                 forum_id = None
+         done = request.registry['res.users'].process_forum_validation_token(request.cr, request.uid, token, int(id), email, forum_id=forum_id, context=request.context)
+         if done:
+             request.session['validation_email_done'] = True
+         if forum_id:
+             return request.redirect("/forum/%s" % int(forum_id))
+         return request.redirect('/forum')
+     @http.route('/forum/validate_email/close', type='json', auth='public', website=True)
+     def validate_email_done(self):
+         request.session['validation_email_done'] = False
+         return True
      # Forum
      # --------------------------------------------------
  
                   '''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag", "[('forum_id','=',forum[0])]"):tag>/questions''',
                   '''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag", "[('forum_id','=',forum[0])]"):tag>/questions/page/<int:page>''',
                   ], type='http', auth="public", website=True)
 -    def questions(self, forum, tag=None, page=1, filters='all', sorting='date', search='', **post):
 +    def questions(self, forum, tag=None, page=1, filters='all', sorting=None, search='', post_type=None, **post):
          cr, uid, context = request.cr, request.uid, request.context
          Post = request.registry['forum.post']
          user = request.registry['res.users'].browse(cr, uid, uid, context=context)
              domain += [('child_ids', '=', False)]
          elif filters == 'followed':
              domain += [('message_follower_ids', '=', user.partner_id.id)]
 -        else:
 -            filters = 'all'
 -
 -        if sorting == 'answered':
 -            order = 'child_count desc'
 -        elif sorting == 'vote':
 -            order = 'vote_count desc'
 -        elif sorting == 'date':
 -            order = 'write_date desc'
 -        else:
 -            sorting = 'creation'
 -            order = 'create_date desc'
 +
 +        if post_type:
 +            domain += [('type', '=', post_type)]
 +        if not sorting:
 +            sorting = forum.default_order
  
          question_count = Post.search(cr, uid, domain, count=True, context=context)
          if tag:
          else:
              url = "/forum/%s" % slug(forum)
  
 -        url_args = {}
 +        url_args = {
 +            'sorting': sorting
 +        }
          if search:
              url_args['search'] = search
          if filters:
              url_args['filters'] = filters
 -        if sorting:
 -            url_args['sorting'] = sorting
          pager = request.website.pager(url=url, total=question_count, page=page,
                                        step=self._post_per_page, scope=self._post_per_page,
                                        url_args=url_args)
  
 -        obj_ids = Post.search(cr, uid, domain, limit=self._post_per_page, offset=pager['offset'], order=order, context=context)
 +        obj_ids = Post.search(cr, uid, domain, limit=self._post_per_page, offset=pager['offset'], order=sorting, context=context)
          question_ids = Post.browse(cr, uid, obj_ids, context=context)
  
          values = self._prepare_forum_values(forum=forum, searches=post)
              'filters': filters,
              'sorting': sorting,
              'search': search,
 +            'post_type': post_type,
          })
          return request.website.render("website_forum.forum_index", values)
  
      # Questions
      # --------------------------------------------------
  
 -    @http.route(['/forum/<model("forum.forum"):forum>/ask'], type='http', auth="public", website=True)
 -    def question_ask(self, forum, **post):
 -        if not request.session.uid:
 -            return login_redirect()
 -        values = self._prepare_forum_values(forum=forum, searches={},  header={'ask_hide': True})
 -        return request.website.render("website_forum.ask_question", values)
 -
 -    @http.route('/forum/<model("forum.forum"):forum>/question/new', type='http', auth="user", methods=['POST'], website=True)
 -    def question_create(self, forum, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        Tag = request.registry['forum.tag']
 -        question_tag_ids = []
 -        if post.get('question_tags').strip('[]'):
 -            tags = post.get('question_tags').strip('[]').replace('"', '').split(",")
 -            for tag in tags:
 -                tag_ids = Tag.search(cr, uid, [('name', '=', tag)], context=context)
 -                if tag_ids:
 -                    question_tag_ids.append((4, tag_ids[0]))
 -                else:
 -                    question_tag_ids.append((0, 0, {'name': tag, 'forum_id': forum.id}))
 -
 -        new_question_id = request.registry['forum.post'].create(
 -            request.cr, request.uid, {
 -                'forum_id': forum.id,
 -                'name': post.get('question_name'),
 -                'content': post.get('content'),
 -                'tag_ids': question_tag_ids,
 -            }, context=context)
 -        return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), new_question_id))
 +    @http.route('/forum/get_url_title', type='json', auth="user", methods=['POST'], website=True)
 +    def get_url_title(self, **kwargs):
 +        arch = lxml.html.parse(urlopen(kwargs.get('url')))
 +        return arch.find(".//title").text
  
      @http.route(['''/forum/<model("forum.forum"):forum>/question/<model("forum.post", "[('forum_id','=',forum[0]),('parent_id','=',False)]"):question>'''], type='http', auth="public", website=True)
      def question(self, forum, question, **post):
              'forum': forum,
              'reasons': reasons,
          })
 -        return request.website.render("website_forum.close_question", values)
 +        return request.website.render("website_forum.close_post", values)
  
      @http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/edit_answer', type='http', auth="user", website=True)
      def question_edit_answer(self, forum, question, **kwargs):
      # Post
      # --------------------------------------------------
  
 -    @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/new', type='http', auth="public", methods=['POST'], website=True)
 -    def post_new(self, forum, post, **kwargs):
 +    @http.route(['/forum/<model("forum.forum"):forum>/<post_type>'], type='http', auth="public", website=True)
 +    def forum_post(self, forum, post_type, **post):
          if not request.session.uid:
              return login_redirect()
          cr, uid, context = request.cr, request.uid, request.context
          user = request.registry['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
          if not user.email or not tools.single_email_re.match(user.email):
              return werkzeug.utils.redirect("/forum/%s/user/%s/edit?email_required=1" % (slug(forum), uid))
 -        request.registry['forum.post'].create(
 -            request.cr, request.uid, {
 +        values = self._prepare_forum_values(forum=forum, searches={},  header={'ask_hide': True})
 +        return request.website.render("website_forum.%s" % post_type, values)
 +
 +    @http.route(['/forum/<model("forum.forum"):forum>/<post_type>/new',
 +                 '/forum/<model("forum.forum"):forum>/<model("forum.post"):post_parent>/reply']
 +                , type='http', auth="public", methods=['POST'], website=True)
 +    def post_create(self, forum, post_parent='', post_type='', **post):
 +        cr, uid, context = request.cr, request.uid, request.context
 +        if not request.session.uid:
 +            return login_redirect()
 +
 +        post_tag_ids = []
 +        Tag = request.registry['forum.tag']
 +        if post.get('post_tags', False) and post.get('post_tags').strip('[]'):
 +            tags = post.get('post_tags').strip('[]').replace('"', '').split(",")
 +            for tag in tags:
 +                tag_ids = Tag.search(cr, uid, [('name', '=', tag)], context=context)
 +                if tag_ids:
 +                    post_tag_ids.append((4, tag_ids[0]))
 +                else:
 +                    post_tag_ids.append((0, 0, {'name': tag, 'forum_id': forum.id}))
 +
 +        new_question_id = request.registry['forum.post'].create(cr, uid, {
                  'forum_id': forum.id,
 -                'parent_id': post.id,
 -                'content': kwargs.get('content'),
 -            }, context=request.context)
 -        return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(post)))
 +                'name': post.get('post_name', ''),
 +                'content': post.get('content', False),
 +                'content_link': post.get('content_link', False),
 +                'parent_id': post_parent and post_parent.id or False,
 +                'tag_ids': post_tag_ids,
 +                'type': post_parent and post_parent.type or post_type,
 +            }, context=context)
 +        return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), post_parent and slug(post_parent) or new_question_id))
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/comment', type='http', auth="public", methods=['POST'], website=True)
      def post_comment(self, forum, post, **kwargs):
          cr, uid, context = request.cr, request.uid, request.context
          if kwargs.get('comment') and post.forum_id.id == forum.id:
              # TDE FIXME: check that post_id is the question or one of its answers
-             request.registry['forum.post']._post_comment(
-                 cr, uid, post,
+             request.registry['forum.post'].message_post(
+                 cr, uid, post.id,
                  body=kwargs.get('comment'),
-                 context=context)
+                 type='comment',
+                 subtype='mt_comment',
+                 context=dict(context, mail_create_nosubcribe=True))
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/save', type='http', auth="user", methods=['POST'], website=True)
      def post_save(self, forum, post, **kwargs):
          cr, uid, context = request.cr, request.uid, request.context
 -        question_tags = []
 -        if kwargs.get('question_tag') and kwargs.get('question_tag').strip('[]'):
 +        post_tags = []
 +        if kwargs.get('post_tag') and kwargs.get('post_tag').strip('[]'):
              Tag = request.registry['forum.tag']
 -            tags = kwargs.get('question_tag').strip('[]').replace('"', '').split(",")
 +            tags = kwargs.get('post_tag').strip('[]').replace('"', '').split(",")
              for tag in tags:
                  tag_ids = Tag.search(cr, uid, [('name', '=', tag)], context=context)
                  if tag_ids:
 -                    question_tags += tag_ids
 +                    post_tags += tag_ids
                  else:
                      new_tag = Tag.create(cr, uid, {'name': tag, 'forum_id': forum.id}, context=context)
 -                    question_tags.append(new_tag)
 +                    post_tags.append(new_tag)
          vals = {
 -            'tag_ids': [(6, 0, question_tags)],
 -            'name': kwargs.get('question_name'),
 +            'tag_ids': [(6, 0, post_tags)],
 +            'name': kwargs.get('post_name'),
              'content': kwargs.get('content'),
          }
          request.registry['forum.post'].write(cr, uid, [post.id], vals, context=context)
@@@ -76,7 -76,7 +76,7 @@@
  
          <!-- Reasons for closing Post -->
          <record id="reason_1" model="forum.post.reason">
 -            <field name="name">duplicate question</field>
 +            <field name="name">duplicate post</field>
          </record>
          <record id="reason_2" model="forum.post.reason">
              <field name="name">off-topic or not relevant</field>
@@@ -85,7 -85,7 +85,7 @@@
              <field name="name">too subjective and argumentative</field>
          </record>
          <record id="reason_4" model="forum.post.reason">
 -            <field name="name">not a real question</field>
 +            <field name="name">not a real post</field>
          </record>
          <record id="reason_6" model="forum.post.reason">
              <field name="name">not relevant or out dated</field>
              <field name="name">too localized</field>
          </record>
  
+         <!-- Email template for email validation (for karma purpose) -->
+         <record id="validation_email" model="email.template">
+             <field name="name">Email Verification</field>
+             <field name="model_id" ref="base.model_res_users"/>
+             <field name="email_from"><![CDATA[${object.company_id.name} <${(object.company_id.email or user.email)|safe}>]]></field>
+             <field name="email_to">${object.email|safe}</field>
+             <field name="subject"><![CDATA[${object.company_id.name} Forums validation]]></field>
+             <field name="body_html"><![CDATA[
+ <p>
+     Hello ${object.name},
+ </p>
+ <p>
+     You have been invited to validate your email in order to get access to "${object.company_id.name}" Q/A Forums.
+ </p>
+ <p>
+     To validate your email, please click on the following link:
+ </p>
+ <ul>
+     <li><a href="${ctx.get('token_url')}">Validate my account for "${object.company_id.name}" Q/A Forums</a></li>
+ </ul>
+ <p>
+     Thanks,
+ </p>
+ <pre>
+ --
+ ${object.company_id.name or ''}
+ ${object.company_id.email or ''}
+ ${object.company_id.phone or ''}
+ </pre>]]></field>
+         </record>
      </data>
  </openerp>
@@@ -1,16 -1,18 +1,18 @@@
  # -*- coding: utf-8 -*-
  
  from datetime import datetime
+ import uuid
+ from werkzeug.exceptions import Forbidden
  
  import openerp
- from openerp import tools
+ from openerp import api, tools
  from openerp import SUPERUSER_ID
  from openerp.addons.website.models.website import slug
+ from openerp.exceptions import Warning
  from openerp.osv import osv, fields
  from openerp.tools import html2plaintext
  from openerp.tools.translate import _
  
- from werkzeug.exceptions import Forbidden
  
  class KarmaError(Forbidden):
      """ Karma-related error, used for forum and posts. """
@@@ -23,35 -25,27 +25,41 @@@ class Forum(osv.Model)
      _description = 'Forums'
      _inherit = ['mail.thread', 'website.seo.metadata']
  
+     def init(self, cr):
+         """ Add forum uuid for user email validation. """
+         forum_uuids = self.pool['ir.config_parameter'].search(cr, SUPERUSER_ID, [('key', '=', 'website_forum.uuid')])
+         if not forum_uuids:
+             self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'website_forum.uuid', str(uuid.uuid4()), ['base.group_system'])
      _columns = {
 -        'name': fields.char('Name', required=True, translate=True),
 +        'name': fields.char('Forum Name', required=True, translate=True),
          'faq': fields.html('Guidelines'),
          'description': fields.html('Description'),
 +        'introduction_message': fields.html('Introduction Message'),
 +        'relevancy_option_first': fields.float('First Relevancy Parameter'),
 +        'relevancy_option_second': fields.float('Second Relevancy Parameter'),
 +        'default_order': fields.selection([
 +            ('create_date desc','Newest'),
 +            ('write_date desc','Last Updated'),
 +            ('vote_count desc','Most Voted'),
 +            ('relevancy desc','Relevancy'),
 +            ('child_count desc','Answered'),
 +            ], 'Default Order', required=True),
 +        'default_allow': fields.selection([('post_link','Link'),('ask_question','Question'),('post_discussion','Discussion')], 'Default Post', required=True),
 +        'allow_link': fields.boolean('Links', help="When clicking on the post, it redirects to an external link"),
 +        'allow_question': fields.boolean('Questions', help="Users can answer only once per question. Contributors can edit answers and mark the right ones."),
 +        'allow_discussion': fields.boolean('Discussions'),
          # karma generation
-         'karma_gen_question_new': fields.integer('Post a Questions'),
-         'karma_gen_question_upvote': fields.integer('Upvote a Question'),
-         'karma_gen_question_downvote': fields.integer('Downvote a Question'),
-         'karma_gen_answer_upvote': fields.integer('Upvote an Answer'),
-         'karma_gen_answer_downvote': fields.integer('Downvote an answer'),
-         'karma_gen_answer_accept': fields.integer('Accept an Answer'),
-         'karma_gen_answer_accepted': fields.integer('Have Your Answer Accepted'),
-         'karma_gen_answer_flagged': fields.integer('Have Your Answer Flagged'),
+         'karma_gen_question_new': fields.integer('Asking a question'),
+         'karma_gen_question_upvote': fields.integer('Question upvoted'),
+         'karma_gen_question_downvote': fields.integer('Question downvoted'),
+         'karma_gen_answer_upvote': fields.integer('Answer upvoted'),
+         'karma_gen_answer_downvote': fields.integer('Answer downvoted'),
+         'karma_gen_answer_accept': fields.integer('Accepting an answer'),
+         'karma_gen_answer_accepted': fields.integer('Answer accepted'),
+         'karma_gen_answer_flagged': fields.integer('Answer flagged'),
          # karma-based actions
 -        'karma_ask': fields.integer('Ask a question'),
 +        'karma_ask': fields.integer('Ask a new question'),
          'karma_answer': fields.integer('Answer a question'),
          'karma_edit_own': fields.integer('Edit its own posts'),
          'karma_edit_all': fields.integer('Edit all posts'),
          'karma_upvote': fields.integer('Upvote'),
          'karma_downvote': fields.integer('Downvote'),
          'karma_answer_accept_own': fields.integer('Accept an answer on its own questions'),
-         'karma_answer_accept_all': fields.integer('Accept an answers to all questions'),
+         'karma_answer_accept_all': fields.integer('Accept an answer to all questions'),
          'karma_editor_link_files': fields.integer('Linking files (Editor)'),
 -        'karma_editor_clickable_link': fields.integer('Clickable links (Editor)'),
 +        'karma_editor_clickable_link': fields.integer('Add clickable links (Editor)'),
          'karma_comment_own': fields.integer('Comment its own posts'),
          'karma_comment_all': fields.integer('Comment all posts'),
          'karma_comment_convert_own': fields.integer('Convert its own answers to comments and vice versa'),
-         'karma_comment_convert_all': fields.integer('Convert all answers to answers and vice versa'),
+         'karma_comment_convert_all': fields.integer('Convert all answers to comments and vice versa'),
          'karma_comment_unlink_own': fields.integer('Unlink its own comments'),
          'karma_comment_unlink_all': fields.integer('Unlink all comments'),
          'karma_retag': fields.integer('Change question tags'),
          return False
  
      _defaults = {
 +        'default_order': 'write_date desc',
 +        'allow_question': True,
 +        'default_allow': 'ask_question',
 +        'allow_link': False,
 +        'allow_discussion': False,
          'description': 'This community is for professionals and enthusiasts of our products and services.',
          'faq': _get_default_faq,
+         'karma_gen_question_new': 0,  # set to null for anti spam protection
 +        'introduction_message': """<h1 class="mt0">Welcome!</h1>
 +                  <p> This community is for professionals and enthusiasts of our products and services.
 +                      Share and discuss the best content and new marketing ideas,
 +                      build your professional profile and become a better marketer together.
 +                  </p>""",
 +        'relevancy_option_first': 0.8,
 +        'relevancy_option_second': 1.8,
-         'karma_gen_question_new': 2,
          'karma_gen_question_upvote': 5,
          'karma_gen_question_downvote': -2,
          'karma_gen_answer_upvote': 10,
          'karma_gen_answer_accept': 2,
          'karma_gen_answer_accepted': 15,
          'karma_gen_answer_flagged': -100,
-         'karma_ask': 0,
-         'karma_answer': 0,
+         'karma_ask': 3,  # set to not null for anti spam protection
+         'karma_answer': 3,  # set to not null for anti spam protection
          'karma_edit_own': 1,
          'karma_edit_all': 300,
          'karma_close_own': 100,
          'karma_answer_accept_all': 500,
          'karma_editor_link_files': 20,
          'karma_editor_clickable_link': 20,
-         'karma_comment_own': 1,
-         'karma_comment_all': 1,
+         'karma_comment_own': 3,
+         'karma_comment_all': 5,
          'karma_comment_convert_own': 50,
          'karma_comment_convert_all': 500,
          'karma_comment_unlink_own': 50,
@@@ -141,14 -123,6 +149,14 @@@ class Post(osv.Model)
      _inherit = ['mail.thread', 'website.seo.metadata']
      _order = "is_correct DESC, vote_count DESC, write_date DESC"
  
 +    def _get_post_relevancy(self, cr, uid, ids, field_name, arg, context):
 +        res = dict.fromkeys(ids, 0)
 +        for post in self.browse(cr, uid, ids, context=context):
 +            days = (datetime.today() - datetime.strptime(post.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days
 +            relavency = abs(post.vote_count - 1) ** post.forum_id.relevancy_option_first / ( days + 2) ** post.forum_id.relevancy_option_second
 +            res[post.id] = relavency if (post.vote_count - 1) >= 0 else -relavency
 +        return res
 +
      def _get_user_vote(self, cr, uid, ids, field_name, arg, context):
          res = dict.fromkeys(ids, 0)
          vote_ids = self.pool['forum.post.vote'].search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
          'name': fields.char('Title'),
          'forum_id': fields.many2one('forum.forum', 'Forum', required=True),
          'content': fields.html('Content'),
 +        'content_link': fields.char('URL', help="URL of Link Articles"),
          'tag_ids': fields.many2many('forum.tag', 'forum_tag_rel', 'forum_id', 'forum_tag_id', 'Tags'),
          'state': fields.selection([('active', 'Active'), ('close', 'Close'), ('offensive', 'Offensive')], 'Status'),
          'views': fields.integer('Number of Views'),
          'active': fields.boolean('Active'),
 +        'type': fields.selection([('question', 'Question'), ('link', 'Article'), ('discussion', 'Discussion')], 'Type'),
 +        'relevancy': fields.function(
 +            _get_post_relevancy, string="Relevancy", type='float',
 +            store={
 +                'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['vote_ids'], 10),
 +                'forum.post.vote': (_get_post_from_vote, [], 10),
 +            }),
          'is_correct': fields.boolean('Valid Answer', help='Correct Answer or Answer on this question accepted.'),
          'website_message_ids': fields.one2many(
              'mail.message', 'res_id',
          'state': 'active',
          'views': 0,
          'active': True,
 +        'type': 'question',
          'vote_ids': list(),
          'favourite_ids': list(),
          'child_ids': list(),
          create_context = dict(context, mail_create_nolog=True)
          post_id = super(Post, self).create(cr, uid, vals, context=create_context)
          post = self.browse(cr, uid, post_id, context=context)
 +        # deleted or closed questions
 +        if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active == False):
 +            osv.except_osv(_('Error !'), _('Posting answer on [Deleted] or [Closed] question is prohibited'))
          # karma-based access
          if not post.parent_id and not post.can_ask:
              raise KarmaError('Not enough karma to create a new question')
          return super(Post, self).unlink(cr, uid, ids, context=context)
  
      def vote(self, cr, uid, ids, upvote=True, context=None):
-         posts = self.browse(cr, uid, ids, context=context)
-         if upvote and any(not post.can_upvote for post in posts):
-             raise KarmaError('Not enough karma to upvote.')
-         elif not upvote and any(not post.can_downvote for post in posts):
-             raise KarmaError('Not enough karma to downvote.')
          Vote = self.pool['forum.post.vote']
          vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
          new_vote = '1' if upvote else '-1'
          res_id = post.parent_id and "%s#answer-%s" % (post.parent_id.id, post.id) or post.id
          return "/forum/%s/question/%s" % (post.forum_id.id, res_id)
  
-     def _post_comment(self, cr, uid, post, body, context=None):
-         context = dict(context or {}, mail_create_nosubcribe=True)
-         if not post.can_comment:
-             raise KarmaError('Not enough karma to comment')
-         return self.message_post(cr, uid, post.id,
-                                  body=body,
-                                  type='comment',
-                                  subtype='mt_comment',
-                                  context=context)
+     @api.cr_uid_ids_context
+     def message_post(self, cr, uid, thread_id, type='notification', subtype=None, context=None, **kwargs):
+         if thread_id and type == 'comment':  # user comments have a restriction on karma
+             if isinstance(thread_id, (list, tuple)):
+                 post_id = thread_id[0]
+             else:
+                 post_id = thread_id
+             post = self.browse(cr, uid, post_id, context=context)
+             if not post.can_comment:
+                 raise KarmaError('Not enough karma to comment')
+         return super(Post, self).message_post(cr, uid, thread_id, type=type, subtype=subtype, context=context, **kwargs)
  
  class PostReason(osv.Model):
      _name = "forum.post.reason"
@@@ -603,6 -561,17 +607,17 @@@ class Vote(osv.Model)
      def create(self, cr, uid, vals, context=None):
          vote_id = super(Vote, self).create(cr, uid, vals, context=context)
          vote = self.browse(cr, uid, vote_id, context=context)
+         # own post check
+         if vote.user_id.id == vote.post_id.create_uid.id:
+             raise Warning('Not allowed to vote for its own post')
+         # karma check
+         if vote.vote == '1' and not vote.post_id.can_upvote:
+             raise KarmaError('Not enough karma to upvote.')
+         elif vote.vote == '-1' and not vote.post_id.can_downvote:
+             raise KarmaError('Not enough karma to downvote.')
+         # karma update
          if vote.post_id.parent_id:
              karma_value = self._get_karma_value('0', vote.vote, vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
          else:
      def write(self, cr, uid, ids, values, context=None):
          if 'vote' in values:
              for vote in self.browse(cr, uid, ids, context=context):
+                 # own post check
+                 if vote.user_id.id == vote.post_id.create_uid.id:
+                     raise Warning('Not allowed to vote for its own post')
+                 # karma check
+                 if (values['vote'] == '1' or vote.vote == '-1' and values['vote'] == '0') and not vote.post_id.can_upvote:
+                     raise KarmaError('Not enough karma to upvote.')
+                 elif (values['vote'] == '-1' or vote.vote == '1' and values['vote'] == '0') and not vote.post_id.can_downvote:
+                     raise KarmaError('Not enough karma to downvote.')
+                 # karma update
                  if vote.post_id.parent_id:
                      karma_value = self._get_karma_value(vote.vote, values['vote'], vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
                  else:
@@@ -6,8 -6,8 +6,8 @@@ $(document).ready(function () 
                  ev.preventDefault();
                  var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="karma_alert">'+
                      '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
-                     karma + ' karma is required to perform this action. You can earn karma by answering questions or having '+
-                     'your answers upvoted by the community.</div>');
+                     karma + ' karma is required to perform this action. You can earn karma by having '+
+                             'your answers upvoted by the community.</div>');
                  var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
                  if (vote_alert.length == 0) {
                      $(ev.currentTarget).parent().append($warning);
@@@ -50,7 -50,6 +50,6 @@@
                          }
                      }
                  });
-             return true;
          });
  
          $('.accept_answer').not('.karma_required').on('click', function (ev) {
@@@ -76,7 -75,6 +75,6 @@@
                      }
                  }
              });
-             return true;
          });
  
          $('.favourite_question').on('click', function (ev) {
@@@ -89,7 -87,6 +87,6 @@@
                      $link.removeClass("forum_favourite_question")
                  }
              });
-             return true;
          });
  
          $('.comment_delete').on('click', function (ev) {
              openerp.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
                  $link.parents('.comment').first().remove();
              });
-             return true;
          });
  
          $('.notification_close').on('click', function (ev) {
              ev.preventDefault();
              var $link = $(ev.currentTarget);
              openerp.jsonRpc("/forum/notification_read", 'call', {
-                 'notification_id': $link.attr("id")})
-             return true;
+                 'notification_id': $link.attr("id")});
+         });
+         $('.send_validation_email').on('click', function (ev) {
+             ev.preventDefault();
+             var $link = $(ev.currentTarget);
+             openerp.jsonRpc("/forum/send_validation_email", 'call', {
+                 'forum_id': $link.attr('forum-id'),
+             }).then(function (data) {
+                 if (data) {
+                     $('button.validation_email_close').click();
+                 }
+             });
+         });
+         $('.validated_email_close').on('click', function (ev) {
+             openerp.jsonRpc("/forum/validate_email/close", 'call', {});
          });
  
 +        $('.js_close_intro').on('click', function (ev) {
 +            ev.preventDefault();
 +            document.cookie = "no_introduction_message = false";
 +            return true;
 +        });
 +
 +        $('.link_url').on('change', function (ev) {
 +            ev.preventDefault();
 +            var $link = $(ev.currentTarget);
 +            if ($link.attr("value").search("^http(s?)://.*")) {
 +                var $warning = $('<div class="alert alert-danger alert-dismissable" style="position:absolute; margin-top: -180px; margin-left: 90px;">'+
 +                    '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
 +                    'Please enter valid URl.'+
 +                    '</div>');
 +                $link.parent().append($warning);
 +                $link.parent().find("button#btn_post_your_article")[0].disabled = true;
 +                $link.parent().find("input[name='content']")[0].value = '';
 +            } else {
 +                openerp.jsonRpc("/forum/get_url_title", 'call', {'url': $link.attr("value")}).then(function (data) {
 +                    $link.parent().find("input[name='content']")[0].value = data;
 +                    $('button').prop('disabled', false);
 +                    $('input').prop('readonly', false);
 +                });
 +            }
 +        });
 +
          if($('input.load_tags').length){
              var tags = $("input.load_tags").val();
              $("input.load_tags").val("");
          function set_tags(tags) {
              $("input.load_tags").textext({
                  plugins: 'tags focus autocomplete ajax',
 +                ext: {
 +                    autocomplete: {
 +                        onSetSuggestions : function(e, data) {
 +                            var self        = this,
 +                                val         = self.val(),
 +                                suggestions = self._suggestions = data.result;
 +                            if(data.showHideDropdown !== false)
 +                                self.trigger(suggestions === null || suggestions.length === 0 && val.length === 0 ? "hideDropdown" : "showDropdown");
 +                        },
 +                        renderSuggestions: function(suggestions) {
 +                            var self = this,
 +                                val  = self.val();
 +                            self.clearItems();
 +                            $.each(suggestions || [], function(index, item) {
 +                                self.addSuggestion(item);
 +                            });
 +                            var lowerCasesuggestions = $.map(suggestions, function(n,i){return n.toLowerCase();});
 +                            if(jQuery.inArray(val.toLowerCase(), lowerCasesuggestions) ==-1) {
 +                                self.addSuggestion("Create '" + val + "'");
 +                            }
 +                        },
 +                    },
 +                    tags: {
 +                        onEnterKeyPress: function(e) {
 +                            var self = this,
 +                                val  = self.val(),
 +                                tag  = self.itemManager().stringToItem(val);
 +
 +                            if(self.isTagAllowed(tag)) {
 +                                tag = tag.replace(/Create\ '|\'|'/g,'');
 +                                self.addTags([ tag ]);
 +                                // refocus the textarea just in case it lost the focus
 +                                self.core().focusInput();
 +                            }
 +                        },
 +                    }
 +                },
                  tagsItems: tags.split(","),
                  //Note: The following list of keyboard keys is added. All entries are default except {32 : 'whitespace!'}.
                  keys: {8: 'backspace', 9: 'tab', 13: 'enter!', 27: 'escape!', 37: 'left', 38: 'up!', 39: 'right',
 -                    40: 'down!', 46: 'delete', 108: 'numpadEnter', 32: 'whitespace!'},
 +                    40: 'down!', 46: 'delete', 108: 'numpadEnter', 32: 'whitespace'},
                  ajax: {
                      url: '/forum/get_tags',
                      dataType: 'json',
                      cacheResults: true
                  }
              });
 -            // Adds: create tags on space + blur
 -            $("input.load_tags").on('whitespaceKeyDown blur', function () {
 -                $(this).textext()[0].tags().addTags([ $(this).val() ]);
 -                $(this).val("");
 -            });
 +
              $("input.load_tags").on('isTagAllowed', function(e, data) {
                  if (_.indexOf($(this).textext()[0].tags()._formData, data.tag) != -1) {
                      data.result = false;
          }
  
          if ($('textarea.load_editor').length) {
 -            var editor = CKEDITOR.instances['content'];
 -            editor.on('instanceReady', CKEDITORLoadComplete);
 +            $('textarea.load_editor').each(function () {
 +                if (this['id']) {
 +                    CKEDITOR.replace(this['id']).on('instanceReady', CKEDITORLoadComplete);
 +                }
 +            });
          }
      }
  });
@@@ -12,9 -12,6 +12,9 @@@
              <field name="arch" type="xml">
                  <tree string="Forums">
                      <field name="name"/>
 +                    <field name="allow_question"/>
 +                    <field name="allow_link"/>
 +                    <field name="allow_discussion"/>
                  </tree>
              </field>
          </record>
              <field name="arch" type="xml">
                  <form string="Forum">
                      <sheet>
 -                        <group>
 +                        <label for="name" class="oe_edit_only"/>
 +                        <h1>
                              <field name="name"/>
 +                        </h1>
-                         <group>
-                             <group string="Post Types">
+                         </group>
+                         <notebook>
++                            <page string="Options">
++                              <group string="Post Types">
 +                                <field name="allow_question"/>
 +                                <field name="allow_link"/>
 +                                <field name="allow_discussion"/>
 +                                <field name="default_allow"/>
-                             </group>
-                             <group string="Orders">
++                              </group>
++                              <group string="Orders">
 +                                <field name="default_order"/>
 +                                <label for="relevancy_option_first" string="Relevancy Computation"/>
 +                                <div>
 +                                    (votes - 1) ** <field name="relevancy_option_first" class="oe_inline"/> / (days + 2) ** <field name="relevancy_option_second" class="oe_inline"/>
 +                                </div>
-                             </group>
-                         </group>
-                         <group>
-                             <group string="Earn Karma">
-                                 <field name="karma_gen_question_new"/>
-                                 <field name="karma_gen_question_upvote"/>
-                                 <field name="karma_gen_question_downvote"/>
-                                 <field name="karma_gen_answer_upvote"/>
-                                 <field name="karma_gen_answer_downvote"/>
-                                 <field name="karma_gen_answer_accept"/>
-                                 <field name="karma_gen_answer_accepted"/>
-                                 <field name="karma_gen_answer_flagged"/>
-                             </group>
-                             <group string="Karma Related Rights">
-                                 <field name="karma_ask"/>
-                                 <field name="karma_edit_own"/>
-                                 <field name="karma_edit_all"/>
-                                 <field name="karma_close_own"/>
-                                 <field name="karma_close_all"/>
-                                 <field name="karma_unlink_own"/>
-                                 <field name="karma_unlink_all"/>
-                                 <field name="karma_upvote"/>
-                                 <field name="karma_downvote"/>
-                                 <field name="karma_answer_accept_own"/>
-                                 <field name="karma_answer_accept_all"/>
-                                 <field name="karma_editor_link_files"/>
-                                 <field name="karma_editor_clickable_link"/>
-                                 <field name="karma_comment_own"/>
-                                 <field name="karma_comment_all"/>
-                                 <field name="karma_comment_convert_own"/>
-                                 <field name="karma_comment_convert_all"/>
-                                 <field name="karma_comment_unlink_own"/>
-                                 <field name="karma_comment_unlink_all"/>
-                             </group>
-                         </group>
++                              </group>
++                            </page>
+                             <page string='Karma Gains'>
+                                 <group>
+                                     <field name="karma_gen_question_new"/>
+                                     <field name="karma_gen_question_upvote"/>
+                                     <field name="karma_gen_question_downvote"/>
+                                     <field name="karma_gen_answer_upvote"/>
+                                     <field name="karma_gen_answer_downvote"/>
+                                     <field name="karma_gen_answer_accept"/>
+                                     <field name="karma_gen_answer_accepted"/>
++                                    <field name="karma_gen_answer_flagged"/>
+                                 </group>
+                             </page>
 -                            <page string='Karma Requirements'>
++                            <page string='Karma Related Rights'>
+                                 <group>
+                                     <group>
+                                         <field name="karma_ask"/>
+                                         <field name="karma_upvote"/>
+                                         <field name="karma_downvote"/>
+                                         <field name="karma_edit_own"/>
+                                         <field name="karma_edit_all"/>
+                                         <field name="karma_close_own"/>
+                                         <field name="karma_close_all"/>
+                                         <field name="karma_unlink_own"/>
+                                         <field name="karma_unlink_all"/>
+                                     </group>
+                                     <group>
+                                         <field name="karma_answer_accept_own"/>
+                                         <field name="karma_answer_accept_all"/>
++                                        <field name="karma_editor_link_files"/>
++                                        <field name="karma_editor_clickable_link"/>
+                                         <field name="karma_comment_own"/>
+                                         <field name="karma_comment_all"/>
+                                         <field name="karma_comment_convert_own"/>
+                                         <field name="karma_comment_convert_all"/>
+                                         <field name="karma_comment_unlink_own"/>
+                                         <field name="karma_comment_unlink_all"/>
+                                     </group>
+                                 </group>
+                             </page>
+                         </notebook>
                      </sheet>
                      <div class="oe_chatter">
                          <field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
                                  <field name="vote_count"/>
                                  <field name="favourite_count"/>
                                  <field name="child_count"/>
 +                                <field name="relevancy"/>
                              </group>
                          </group>
                      </sheet>
@@@ -2,33 -2,6 +2,33 @@@
  <openerp>
      <data>
  
 +<!-- Editor custom -->
 +<template id="assets_editor" inherit_id="website.assets_editor" name="Forum Editor Assets" groups="base.group_user">
 +    <xpath expr="." position="inside">
 +        <script type="text/javascript" src="/website_forum/static/src/js/website.tour.forum.js"/>
 +        <script type="text/javascript" src="/website_forum/static/src/js/website_forum.editor.js"/>
 +    </xpath>
 +</template>
 +
 +<!-- Front-end custom css / js + ckeditor lib and customization -->
 +<template id="assets_frontend" inherit_id="website.assets_frontend" name="Forum Assets">
 +    <xpath expr="." position="inside">
 +        <link rel='stylesheet' href="/web/static/lib/jquery.textext/jquery.textext.css"/>
 +        <link rel='stylesheet' href='/website_forum/static/src/css/website_forum.css'/>
 +        <script type="text/javascript" src="/website_forum/static/src/js/website_forum.js"/>
 +        <script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"/>
 +        <script type="text/javascript">
 +            var CKEDITOR_BASEPATH = '/web/static/lib/ckeditor/';
 +        </script>
 +        <script type="text/javascript" src="/web/static/lib/ckeditor/ckeditor.js"></script>
 +         <script type="text/javascript">
 +                CKEDITOR.config.toolbar = [['Bold','Italic','Underline','Strike'],['NumberedList','BulletedList', 'Blockquote']
 +                ,['Outdent','Indent','Link','Unlink','Image'],] ;
 +        </script>
 +
 +    </xpath>
 +</template>
 +
  <!-- Layout add nav and footer -->
  <template id="header_footer_custom" inherit_id="website.footer_default"
      name="Footer Questions Link">
      </form>
  </template>
  
  <!-- Page Index -->
  <template id="header" name="Forum Index">
      <t t-call="website.layout">
 -        <t t-set="head">
 -            <script type="text/javascript" src="/web/static/lib/ckeditor/ckeditor.js"/>
 -            <script type="text/javascript">
 -                CKEDITOR.config.toolbar = [['Bold','Italic','Underline','Strike'],['NumberedList','BulletedList', 'Blockquote']
 -                ,['Outdent','Indent','Link','Unlink','Image'],] ;
 -            </script>
 -        </t>
 +        <div t-if="is_public_user and not no_introduction_message" class="alert alert-success alert-dismissable">
 +            <div class="container">
 +                <div t-field="forum.introduction_message"/>
 +                <a class='btn btn-primary' t-attf-href="/web?redirect=#{ request.httprequest.url }">Register</a>
 +                <button type="button" class="btn btn-link js_close_intro" data-dismiss="alert" aria-hidden="true">Hide Intro</button>
 +            </div>
 +        </div>
          <div class="container mt16 website_forum">
              <div class="navbar navbar-default">
                  <div class="navbar-header">
                      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#oe-help-navbar-collapse">
                          <span class="sr-only">Toggle navigation</span>
                          <span class="icon-bar"></span>
 -                        <!-- <span class="icon-bar"></span> -->
                          <span class="icon-bar"></span>
                      </button>
                      <a class="navbar-brand" t-attf-href="/forum/#{slug(forum)}">
                  </div>
                  <div class="collapse navbar-collapse" id="oe-help-navbar-collapse">
                      <ul class="nav navbar-nav">
 -                        <li t-att-class="filters in ('all', 'unanswered','followed','question','tag') and 'active' or '' ">
 -                            <a t-attf-href="/forum/#{ slug(forum) }">Questions</a>
 +                        <li t-att-class="sorting == 'relevancy desc' and 'active' or '' ">
 +                            <a t-attf-href="/forum/#{ slug(forum) }?{{ keep_query( 'search', 'post_type', 'filters', sorting='relevancy desc') }}">Trending</a>
 +                        </li>
 +                        <li t-att-class="sorting == 'create_date desc' and 'active' or '' ">
 +                            <a t-attf-href="/forum/#{ slug(forum) }?{{ keep_query( 'search', 'post_type', 'filters', sorting='create_date desc') }}">Newest</a>
                          </li>
                          <li t-att-class="searches.get('users') and 'active' or '' ">
                              <a t-attf-href="/forum/#{ slug(forum) }/users">People</a>
                          </li>
                      </ul>
                      <form class="navbar-form navbar-right" role="search" t-attf-action="/forum/#{ slug(forum) }" method="get">
 -                        <div class="form-group">
 -                            <input type="search" class="form-control"
 -                                name="search" placeholder="Search a question..."
 -                                t-att-value="search or ''"/>
 -                            <button type="submit" class="btn btn-default">Search</button>
 +                        <div class="input-group">
 +                            <input type="search" class="form-control" name="search" t-att-value="search or ''"/>
 +                            <span class="input-group-btn">
 +                                <button type="submit" class="btn btn-default">Search</button>
 +                            </span>
                          </div>
                      </form>
                  </div>
                          <div t-field="notification.body"/>
                          <a t-attf-href="/forum/#{ slug(forum) }/user/#{ user.id }#badges" class="fa fa-arrow-right">View Your Badges</a>
                      </div>
+                     <div t-if="not validation_email_sent and not is_public_user and user.karma == 0" class="alert alert-danger alert-dismissable">
+                         <button type="button" class="close validation_email_close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
+                         <div>
+                             <p>
+                                 It appears your email has not been verified.
+                                 <a class="send_validation_email" href="#" t-att-forum-id="forum.id">Click here to send a verification email allowing you to participate to the forum.</a>
+                             </p>
+                         </div>
+                     </div>
+                     <div t-if="validation_email_done" class="alert alert-success alert-dismissable">
+                         <button type="button" class="close validated_email_close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
+                         <div>
+                             <p>Congratulations! Your email has just been validated. You may now participate to our forums.</p>
+                         </div>
+                     </div>
                      <t t-raw="0"/>
                  </div>
                  <div class="col-sm-3" id="right-column">
-                     <div t-if="not header.get('ask_hide')" t-attf-class="btn-group btn-block mb16 #{user.karma &gt;= forum.karma_ask and '' or 'karma_required'}" t-attf-data-karma="#{forum.karma_ask}">
 -                    <a t-if="not header.get('ask_hide')"
 -                       t-attf-class="btn btn-primary btn-lg btn-block mb16 #{(user.karma &lt; forum.karma_ask) and 'karma_required' or ''}"
 -                       t-attf-href="/forum/#{slug(forum)}/ask"
 -                       t-attf-data-karma="#{forum.karma_ask}">Ask a Question</a>
++                    <div t-if="not header.get('ask_hide')" t-attf-class="btn-group btn-block mb16 #{(user.karma &lt; forum.karma_ask) and 'karma_required' or ''}" t-attf-data-karma="#{forum.karma_ask}">
 +                        <a type="button" class="btn btn-primary btn-lg col-sm-10" t-attf-href="/forum/#{slug(forum)}/#{forum.default_allow}">
 +                            <t t-if="forum.default_allow == 'ask_question'">Ask a Question</t>
 +                            <t t-if="forum.default_allow == 'post_link'">Submit a Post</t>
 +                            <t t-if="forum.default_allow == 'post_discussion'">New Discussion</t>
 +                        </a>
 +                        <button type="button" class="btn btn-primary btn-lg col-sm-2 dropdown-toggle" data-toggle="dropdown">
 +                            <span class="caret"></span>
 +                            <span class="sr-only">Select Post</span>
 +                        </button>
 +                        <ul class="dropdown-menu" role="menu">
 +                            <li t-if="forum.allow_link"><a t-attf-href="/forum/#{slug(forum)}/post_link">Submit a Post</a></li>
 +                            <li t-if="forum.allow_question"><a t-attf-href="/forum/#{slug(forum)}/ask_question">Ask a Question</a></li>
 +                            <li t-if="forum.allow_discussion"><a t-attf-href="/forum/#{slug(forum)}/post_discussion">Launch a Discussion</a></li>
 +                        </ul>
 +                    </div>
                      <div class="panel panel-default">
                          <div class="panel-heading">
                              <h3 class="panel-title">Keep Informed</h3>
                      </div>
                      <div class="panel panel-default" id="about_forum">
                          <div class="panel-heading">
 -                            <h3 class="panel-title">About This Forum</h3>
 +                            <h3 class="panel-title">About This Community</h3>
                          </div>
                          <div class="panel-body">
                              <t t-raw="forum.description"/>
  <template id="display_post">
      <div class="question row">
          <div class="col-md-2 hidden-xs text-center">
 -            <div t-attf-class="box #{question.is_correct and 'oe_green' or 'oe_grey'} #{(question.child_count == 0) and 'text-muted' or ''}">
 -                <span t-esc="question.child_count"/>
 -                <div t-if="question.child_count&gt;1" class="subtitle">Answers</div>
 -                <div t-if="question.child_count&lt;=1" class="subtitle">Answer</div>
 -            </div>
 +            <t t-call="website_forum.vote">
 +                <t t-set="post" t-value="question"/>
 +            </t>
          </div>
          <div class="col-md-10 clearfix">
              <div class="question-name">
 -                <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
 -                    <span t-if="not question.active"><b> [Deleted]</b></span>
 -                    <span t-if="question.state == 'close'"><b> [Closed]</b></span>
 +                <t t-if="question.type == 'link'">
 +                    <a t-att-href="question.content_link" t-raw="question.name"/>
 +                </t>
 +                <t t-if="question.type in ('question', 'discussion')">
 +                    <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
 +                </t>
 +                <span t-if="not question.active"><b> [Deleted]</b></span>
 +                <span t-if="question.state == 'close'"><b> [Closed]</b></span>
              </div>
              <t t-foreach="question.tag_ids" t-as="question_tag">
                  <a t-attf-href="/forum/#{ slug(forum) }/tag/#{slug(question_tag)}/questions">
 -                    <span t-attf-class="pull-right badge #{tag and tag.name == question_tag.name and 'badge-active' ''}" t-field="question_tag.name"
 +                    <span t-attf-class="pull-right label #{tag and tag.name == question_tag.name and 'label-primary' or 'label-default'}" t-field="question_tag.name"
                          style="margin-right: 4px;"/>
                  </a>
              </t>
 -            <div class="text-muted">
 -                by <a t-attf-href="/forum/#{ slug(forum) }/user/#{ question.create_uid.id }"
 -                    t-field="question.create_uid" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'
 -                    style="display: inline-block;"/>
 -                on <span t-field="question.write_date" t-field-options='{"format":"short"}'/>
 -                <span class="visible-xs">
 -                    <b t-esc="question.child_count or 0"/>
 -                    <t t-if="question.child_count&gt;1">answers</t>
 -                    <t t-if="question.child_count==1">answers</t>
 -                </span>
 -                with <b t-field="question.views"/> views
 -                <span t-if="question.vote_count&gt;0"> and
 -                    <b t-esc="question.vote_count or 0"/>
 -                    <t t-if="question.vote_count&gt;1">votes</t>
 -                    <t t-if="question.vote_count==1">vote</t>
 -                </span>
 -            </div>
 +            <small class="text-muted">
 +                By <span t-field="question.create_uid" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}' style="display: inline-block;"/>
 +                â€¢ <span t-field="question.write_date" t-field-options='{"format":"short"}'/>
 +                â€¢ <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
 +                    <t t-esc="question.child_count"/>
 +                    <t t-if="question.child_count&gt;1">comments</t>
 +                    <t t-if="question.child_count&lt;=1">comment</t>
 +                </a>
 +            </small>
          </div>
      </div>
  </template>
  <!-- Specific Forum Layout -->
  <template id="forum_index" name="Forum">
      <t t-call="website_forum.header">
 -        <h1 class="page-header mt0">
 -            <t t-esc="question_count"/> <span>Questions</span>
 +        <h2 class="page-header mt0">
 +            <t t-esc="question_count"/>
 +                <span t-if="post_type == 'all'">Posts</span>
 +                <span t-if="post_type == 'question'">Questions</span>
 +                <span t-if="post_type == 'link'">Posts</span>
 +                <span t-if="post_type == 'discussion'">Discussions</span>
              <t t-esc="search"/>
              <small class="dropdown" t-if="filters in ('all', 'unanswered','followed', 'tag')">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                    <t t-if="filters == 'unanswered'">Unanswered</t>
                    <t t-if="filters == 'followed'">Followed</t>
                    <t t-if="tag"><span t-field="tag.name"/></t>
 -                  <t t-if="sorting == 'date'"> by activity date</t>
 -                  <t t-if="sorting == 'creation'"> by creation date</t>
 -                  <t t-if="sorting == 'answered'"> by most answered</t>
 -                  <t t-if="sorting == 'vote'"> by most voted</t>
 +                  <t t-if="sorting == 'relevancy desc'"> by relevancy</t>
 +                  <t t-if="sorting == 'write_date desc'"> by activity date</t>
 +                  <t t-if="sorting == 'create_date desc'"> by newest</t>
 +                  <t t-if="sorting == 'child_count desc'"> by most answered</t>
 +                  <t t-if="sorting == 'vote_count desc'"> by most voted</t>
                    <b class="caret"/>
                </a>
                <ul class="dropdown-menu">
                        <a href=""><t t-esc="tag.name"/></a>
                    </li>
                    <li class="dropdown-header">Sort by</li>
 -                  <li t-att-class="sorting == 'date' and 'active' or '' ">
 -                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='date')">Last activity date</a>
 +                  <li t-att-class="sorting == 'relevancy desc' and 'active' or '' ">
 +                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='relevancy desc')">Relevancy</a>
 +                  </li>
 +                  <li t-att-class="sorting == 'write_date desc' and 'active' or '' ">
 +                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='write_date desc')">Last activity date</a>
                    </li>
 -                  <li t-att-class="sorting == 'creation' and 'active' or '' ">
 -                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='creation')">Newest</a>
 +                  <li t-att-class="sorting == 'create_date desc' and 'active' or '' ">
 +                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='create_date desc')">Newest</a>
                    </li>
 -                  <li t-att-class="sorting == 'answered' and 'active' or '' ">
 -                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='answered')">Most answered</a>
 +                  <li t-att-class="sorting == 'child_count desc' and 'active' or '' ">
 +                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='child_count desc')">Most answered</a>
                    </li>
 -                  <li t-att-class="sorting == 'vote' and 'active' or '' ">
 -                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='vote')">Most voted</a>
 +                  <li t-att-class="sorting == 'vote_count desc' and 'active' or '' ">
 +                      <a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='vote_count desc')">Most voted</a>
                    </li>
                </ul>
              </small>
 -        </h1>
 +        </h2>
          <div t-foreach="question_ids" t-as="question" class="mb16">
              <t t-call="website_forum.display_post"/>
          </div>
      </t>
  </template>
  
 +<!-- Edition: Post Article -->
 +<template id="post_link">
 +    <t t-call="website_forum.header">
 +        <h1 class="mt0">Submit a Link</h1>
 +        <p class="mb32">
 +            Share an awesome link. Your post will appear in the 'Newest' top-menu.
 +            If the community vote on your post, it will get traction by being promoted
 +            in the homepage.
 +        </p><p>
 +            We keep a high level of quality in showcased posts, only 20% of the submited
 +            posts will be featured.
 +        </p>
 +        <form t-attf-action="/forum/#{ slug(forum) }/link/new" method="post" role="form" class="tag_text form-horizontal">
 +            <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 +            <div class="form-group">
 +                <label class="col-sm-2 control-label" for="content_link">URL to Share</label>
 +                <div class="col-sm-8">
 +                    <input type="text" name="post_name" required="True" t-attf-value="#{post_name}"
 +                        class="form-control mb16 link_url" placeholder="e.g. https://www.odoo.com"/>
 +                </div>
 +            </div>
 +            <div class="form-group">
 +                <label class="col-sm-2 control-label" for="post_name">Post Title</label>
 +                <div class="col-sm-8">
 +                    <input type="text" name="content" readonly="True" required="True" t-attf-value="#{content}"
 +                        class="form-control"/>
 +                </div>
 +            </div>
 +            <div class="form-group">
 +                <label class="col-sm-2 control-label" for="post_tags">Tags</label>
 +                <div class="col-sm-8">
 +                    <input type="text" name="post_tags" readonly="True" class="form-control load_tags"/>
 +                </div>
 +            </div>
 +            <div class="form-group">
 +                <div class="col-sm-offset-2 col-sm-8">
 +                    <button class="btn btn-primary" disabled="True" id="btn_post_your_article">Post Your Article</button>
 +                </div>
 +            </div>
 +        </form>
 +    </t>
 +</template>
 +
 +<!-- Edition: Post your Discussion Topic -->
 +<template id="post_discussion">
 +    <t t-call="website_forum.header">
 +        <h1 class="mt0">Post Your Discussion Topic</h1>
 +        <p>
 +            <b>Share</b> Start Something Awesome
 +        </p>
 +        <form t-attf-action="/forum/#{ slug(forum) }/discussion/new" method="post" role="form" class="tag_text">
 +            <input type="text" name="post_name" required="True" t-attf-value="#{post_name}"
 +                class="form-control mb16" placeholder="Your Discussion Title..."/>
 +            <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 +            <textarea name="content" id="content" required="True" class="form-control load_editor">
 +                <t t-esc="question_content"/>
 +            </textarea>
 +            <br/>
 +            <input type="text" name="post_tags" placeholder="Tags" class="form-control load_tags"/>
 +            <br/>
 +            <button class="btn btn-primary" id="btn_ask_your_question">Post Your Topic</button>
 +        </form>
 +    </t>
 +</template>
 +
  <!-- Edition: ask your question -->
  <template id="ask_question">
      <t t-call="website_forum.header">
 -        <h1 class="mt0">Ask your Question</h1>
 +        <t t-set="head">
 +            <script type="text/javascript">
 +                $(function () {
 +                    $("[data-toggle='popover']").popover();
 +                });
 +            </script>
 +        </t>
 +        <h1 class="mt0">Ask Your Question</h1>
 +        <p>
 +            To improve your chance getting an answer:
 +        </p>
          <ul>
 -            <li> please, try to make your question interesting to others </li>
 -            <li> provide enough details and, if possible, give an example </li>
 -            <li> be clear and concise, avoid unnecessary introductions (Hi, ... Thanks...) </li>
 +            <li>Set a clear, explicit and concise question title
 +                (check
 +                <a href="#" data-placement="top" data-toggle="popover" data-content="Inventory Date Problem, Task remaining hours, Can you help solve solve my tax computation problem in Canada?" title="Click to get bad question samples">bad examples</a>
 +                and
 +                <a href="#" data-placement="bottom" data-toggle="popover" data-content="How to create a physical inventory at an anterior date?, How is the 'remaining hours' field computed on tasks?, How to configure TPS and TVQ's canadian taxes?" title="Click to get good question titles">good examples</a>
 +                ),
 +            </li>
 +            <li>Avoid unnecessary introductions (Hi,... Please... Thanks...),</li>
 +            <li>Provide enough details and, if possible, give an example.</li>
          </ul>
          <form t-attf-action="/forum/#{ slug(forum) }/question/new" method="post" role="form" class="tag_text">
 -            <input type="text" name="question_name" required="True" t-attf-value="#{question_name}"
 -                class="form-control" placeholder="Enter your Question"/>
 -            <h5 class="mt20">Please enter a descriptive question (should finish with a '?')</h5>
 +            <input type="text" name="post_name" required="True" t-attf-value="#{post_name}"
 +                class="form-control mb16" placeholder="Your Question Title..."/>
              <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 -            <textarea name="content" required="True" class="form-control load_editor">
 +            <textarea name="content" required="True" id="content" class="form-control load_editor">
                  <t t-esc="question_content"/>
              </textarea>
              <br/>
 -            <input type="text" name="question_tags" placeholder="Tags" class="form-control load_tags"/>
 +            <input type="text" name="post_tags" placeholder="Tags" class="form-control load_tags"/>
              <br/>
-             <button t-attf-class="btn btn-primary #{(user.karma &lt;= forum.karma_ask) and 'karma_required' or ''}"
+             <button t-attf-class="btn btn-primary #{(user.karma &lt; forum.karma_ask) and 'karma_required' or ''}"
                  id="btn_ask_your_question" t-att-data-karma="forum.karma_ask">Post Your Question</button>
          </form>
 -        <script type="text/javascript">
 -            CKEDITOR.replace("content");
 -        </script>
      </t>
  </template>
  
  <!-- Edition: edit a post -->
  <template id="edit_post">
      <t t-call="website_forum.header">
 -        <h3 t-if="not is_answer">Edit question</h3>
 -        <h3 t-if="is_answer">Edit answer</h3>
 +        <h3 t-if="not is_answer">Edit <span t-field="post.type"/></h3>
 +        <h3 t-if="is_answer">Edit reply</h3>
          <form t-attf-action="/forum/#{slug(forum)}/post/#{slug(post)}/save" method="post" role="form" class="tag_text">
              <div t-if="not is_answer">
 -                <input type="text" name="question_name" id="question_name" required="True"
 -                    t-attf-value="#{post.name}" class="form-control" placeholder="Edit your Question"/>
 -                <h5 class="mt20">Please enter a descriptive question (should finish by a '?')</h5>
 +                <input type="text" name="post_name" required="True"
 +                    t-attf-value="#{post.name}" class="form-control mb8" placeholder="Edit your Post"/>
 +                <h5 t-if="post.type == 'question'" class="mt20">Please enter a descriptive question (should finish by a '?')</h5>
              </div>
              <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 -            <textarea name="content" required="True" class="form-control load_editor">
 +            <textarea name="content" id="content" required="True" class="form-control load_editor">
                  <t t-esc="post.content"/>
              </textarea>
              <div t-if="not is_answer">
                  <br/>
 -                <input type="text" name="question_tag" class="form-control col-md-9 load_tags" placeholder="Tags" t-attf-value="#{tags}"/>
 +                <input type="text" name="post_tag" class="form-control col-md-9 load_tags" placeholder="Tags" t-attf-value="#{tags}"/>
                  <br/>
              </div>
              <button class="btn btn-primary btn-lg">Save</button>
          </form>
      </t>
  </template>
  
 -<!-- Moderation: close a question -->
 -<template id="close_question">
 +<!-- Moderation: close a post -->
 +<template id="close_post">
      <t t-call="website_forum.header">
 -        <h1 class="mt0">Close question</h1>
 +        <h1 class="mt0">Close Post</h1>
          <p class="text-muted">
 -            If you close this question, it will be hidden for most users. Only
 -            users having a high karma can see closed questions to moderate
 +            If you close this post, it will be hidden for most users. Only
 +            users having a high karma can see closed posts to moderate
              them.
          </p>
          <form t-attf-action="/forum/#{ slug(forum) }/question/#{slug(question)}/close" method="post" role="form" class="form-horizontal mt32 mb64">
              <input name="post_id" t-att-value="question.id" type="hidden"/>
              <div class="form-group">
 -                <label class="col-md-3 control-label" for="reason">Question:</label>
 +                <label class="col-md-3 control-label" for="reason">Post:</label>
                  <div class="col-md-8 mt8">
                      <span t-field="question.name"/>
                  </div>
              </div>
              <div class="form-group">
                  <div class="col-md-offset-3 col-md-8">
 -                    <button class="btn btn-primary">Close question</button>
 +                    <button class="btn btn-primary">Close post</button>
                      <span class="text-muted">or</span>
 -                    <a class="btn btn-link" t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">back to question</a>
 +                    <a class="btn btn-link" t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">back to post</a>
                  </div>
              </div>
          </form>
      </t>
  </template>
  
 +<!-- Edition: post a reply -->
 +<template id="post_reply">
 +    <div class="css_editable_mode_hidden">
 +        <form t-attf-id="reply#{ object._name.replace('.','') + '-' + str(object.id) }" class="collapse oe_comment_grey"
 +            t-attf-action="/forum/#{ slug(forum) }/#{slug(object)}/reply" method="post" role="form">
 +            <h3 class="mt10">Your Reply</h3>
 +            <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 +            <textarea name="content" t-attf-id="content-#{str(object.id)}" class="form-control load_editor" required="True"/>
 +            <button class="btn btn-primary" id="btn_ask_your_question">Post Your Reply</button>
 +        </form>
 +    </div>
 +</template>
 +
  <!-- Edition: post an answer -->
  <template id="post_answer">
 -    <h3 class="mt10">Your answer</h3>
 -    <p>
 +    <h3 class="mt10">Your Reply</h3>
 +    <p t-if="question.type == 'question'">
          <b>Please try to give a substantial answer.</b> If you wanted to comment on the question or answer, just
          <b>use the commenting tool.</b> Please remember that you can always <b>revise your answers</b>
          - no need to answer the same question twice. Also, please <b>don't forget to vote</b>
          - it really helps to select the best questions and answers!
      </p>
 -    <form t-attf-action="/forum/#{ slug(forum) }/post/#{slug(question)}/new" method="post" role="form">
 +    <form t-attf-action="/forum/#{ slug(forum) }/#{slug(question)}/reply" method="post" role="form">
          <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 -        <textarea name="content" class="form-control load_editor" required="True"/>
 +        <textarea name="content" t-attf-id="content-#{str(question.id)}" class="form-control load_editor" required="True"/>
          <button t-attf-class="btn btn-primary mt16 #{not question.can_answer and 'karma_required' or ''}"
 -                id="btn_ask_your_question" t-att-data-karma="question.karma_answer">Post Your Answer</button>
 +                id="btn_ask_your_question" t-att-data-karma="question.karma_answer">Post Your Reply</button>
      </form>
 -    <script type="text/javascript">
 -        CKEDITOR.replace("content");
 -    </script>
  </template>
  
  <template id="vote">
  <!-- Specific Post Layout -->
  <template id="post_description_full" name="Question Navigation">
      <t t-call="website_forum.header">
 -        <div t-attf-class="row question #{not question.active and 'alert alert-danger' or ''}">
 +        <div t-attf-class="row question">
              <div class="col-md-2 hidden-xs text-center">
                  <t t-call="website_forum.vote">
                      <t t-set="post" t-value="question"/>
                          t-attf-class="favourite_question no-decoration fa fa-2x fa-star #{question.user_favourite and 'forum_favourite_question' or ''}"/>
                  </div>
              </div>
 -            <div class="col-md-10">
 +            <div t-attf-class="col-md-10 #{not question.active and 'alert alert-danger' or ''}">
                  <h1 class="mt0">
 -                    <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
 +                    <t t-if="question.type == 'link'">
 +                        <a t-att-href="question.content_link" t-raw="question.name"/>
 +                    </t>
 +                    <t t-if="question.type in ('question', 'discussion')">
 +                        <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
 +                    </t>
                      <span t-if="not question.active"><b> [Deleted]</b></span>
                      <span t-if="question.state == 'close'"><b> [Closed]</b></span>
                  </h1>
                  <div class="alert alert-info text-center" t-if="question.state == 'close'">
                      <p class="mt16">
 -                        <b>The question has been closed<t t-if="question.closed_reason_id"> for reason: <i t-esc="question.closed_reason_id.name"/></t></b>
 +                        <b>The <i t-field="question.type"/> has been closed<t t-if="question.closed_reason_id"> for reason: <i t-esc="question.closed_reason_id.name"/></t></b>
                      </p>
                      <t t-if="question.closed_uid">
                          <b>by <a t-attf-href="/forum/#{ slug(forum) }/user/#{ question.closed_uid.id }"
                          </t>
                      </div>
                  </div>
 -                <t t-raw="question.content"/>
 +                <div t-if="question.type != 'link'"><t t-raw="question.content"/></div>
                  <div class="mt16 clearfix">
                      <div class="pull-right">
                          <div class="text-right">
                              <t t-foreach="question.tag_ids" t-as="tag">
 -                                <a t-attf-href="/forum/#{ slug(forum) }/tag/#{ tag.id }/questions" class="badge" t-field="tag.name"/>
 +                                <a t-attf-href="/forum/#{ slug(forum) }/tag/#{ tag.id }/questions" class="label label-default" t-field="tag.name"/>
                              </t>
                          </div>
                          <ul class="list-inline" id="options">
 -                            <li>
 +                            <li t-if="question.type == 'question'">
                                  <a style="cursor: pointer" t-att-data-toggle="question.can_comment and 'collapse' or ''"
                                      t-attf-class="fa fa-comment-o #{not question.can_comment and 'karma_required text-muted' or ''}"
                                      t-attf-data-karma="#{not question.can_comment and question.karma_comment or 0}"
              </div>
          </div>
          <hr/>
 +        <div t-foreach="question.child_ids" t-as="post_answer" class="mt16 mb32">
 +            <t t-call="website_forum.post_answers">
 +                <t t-set="answer" t-value="post_answer"/>
 +            </t>
 +        </div>
 +        <div t-if="question.type != 'question' or question.type == 'question' and not question.uid_has_answered and question.state != 'close' and question.active != False">
 +            <t t-call="website_forum.post_answer"/>
 +        </div>
 +        <div t-if="question.type == 'question' and question.uid_has_answered" class="mb16">
 +            <a class="btn btn-primary" t-attf-href="/forum/#{slug(forum)}/question/#{slug(question)}/edit_answer">Edit Your Previous Answer</a>
 +            <span class="text-muted">(only one answer per question is allowed)</span>
 +        </div>
 +    </t>
 +</template>
  
 -        <div t-foreach="question.child_ids" t-as="answer" class="mt16 mb32">
 -            <a t-attf-id="answer-#{str(answer.id)}"/>
 -            <div t-attf-class="forum_answer row" t-attf-id="answer_#{answer.id}" >
 -                <div class="col-md-2 hidden-xs text-center">
 +<template id="post_answers">
 +    <a t-attf-id="answer-#{str(answer.id)}"/>
 +    <div t-attf-class="forum_answer row" t-attf-id="answer_#{answer.id}" >
 +        <div class="col-md-2 hidden-xs text-center">
 +            <t t-call="website_forum.vote">
 +                <t t-set="post" t-value="answer"/>
 +            </t>
 +            <div t-if="question.type == 'question'" class="text-muted mt8">
 +                <a t-attf-class="accept_answer fa fa-2x fa-check-circle no-decoration #{answer.is_correct and 'oe_answer_true' or 'oe_answer_false'} #{not answer.can_accept and 'karma_required' or ''}"
 +                    t-attf-data-karma="#{answer.karma_accept}"
 +                    t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct"/>
 +            </div>
 +        </div>
 +        <div class="col-md-10 clearfix">
 +            <t t-raw="answer.content"/>
 +            <div class="mt16">
 +                <ul class="list-inline pull-right">
 +                    <li t-if="question.type == 'question'">
 +                        <a t-attf-class="fa fa-comment-o #{not answer.can_comment and 'karma_required text-muted' or ''}"
 +                            t-attf-data-karma="#{not answer.can_comment and answer.karma_comment or 0}"
 +                            style="cursor: pointer" t-att-data-toggle="answer.can_comment and 'collapse' or ''"
 +                            t-attf-data-target="#comment#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Comment
 +                        </a>
 +                    </li>
 +                    <li t-if="question.type != 'question' and not answer.parent_id or answer.parent_id and not answer.parent_id.parent_id">
 +                        <a t-attf-class="fa fa-comment-o #{not answer.can_comment and 'karma_required text-muted' or ''}"
 +                            t-attf-data-karma="#{not answer.can_comment and answer.karma_comment or 0}"
 +                            style="cursor: pointer" data-toggle="collapse"
 +                            t-attf-data-target="#reply#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Reply
 +                        </a>
 +                    </li>
 +                    <li>
 +                        <t t-call="website_forum.link_button">
 +                            <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/edit'"/>
 +                            <t t-set="label" t-value="'Edit'"/>
 +                            <t t-set="classes" t-value="'fa fa-edit'"/>
 +                            <t t-set="karma" t-value="not answer.can_edit and answer.karma_edit or 0"/>
 +                        </t>
 +                    </li>
 +                    <li>
 +                        <t t-call="website_forum.link_button">
 +                            <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/delete'"/>
 +                            <t t-set="label" t-value="'Delete'"/>
 +                            <t t-set="classes" t-value="'fa-trash-o'"/>
 +                            <t t-set="karma" t-value="not answer.can_unlink and answer.karma_unlink or 0"/>
 +                        </t>
 +                    </li>
 +                    <li t-if="question.type == 'question'">
 +                        <t t-call="website_forum.link_button">
 +                            <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/convert_to_comment'"/>
 +                            <t t-set="label" t-value="'Convert as a comment'"/>
 +                            <t t-set="classes" t-value="'fa-magic'"/>
 +                            <t t-set="karma" t-value="not answer.can_comment_convert and answer.karma_comment_convert or 0"/>
 +                        </t>
 +                    </li>
 +                </ul>
 +                <img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{answer.create_uid.id}/avatar"/>
 +                <div>
 +                    <a t-attf-href="/forum/#{ slug(forum) }/user/#{ answer.create_uid.id }"
 +                        t-field="answer.create_uid"
 +                        t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'
 +                        style="display: inline-block;"/>
 +                    <div t-field="answer.create_uid" t-field-options='{"widget": "contact", "badges": true, "fields": ["karma"]}'/>
 +                    <span class="text-muted">Answered on <span t-field="answer.create_date" t-field-options='{"format":"short"}'/></span>
 +                </div>
 +                <div class="visible-xs text-center">
                      <t t-call="website_forum.vote">
                          <t t-set="post" t-value="answer"/>
                      </t>
                              t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct"/>
                      </div>
                  </div>
 -                <div class="col-md-10 clearfix">
 -                    <t t-raw="answer.content"/>
 -                    <div class="mt16">
 -                        <ul class="list-inline pull-right">
 -                            <li>
 -                                <a t-attf-class="fa fa-comment-o #{not answer.can_comment and 'karma_required text-muted' or ''}"
 -                                    t-attf-data-karma="#{not answer.can_comment and answer.karma_comment or 0}"
 -                                    style="cursor: pointer" t-att-data-toggle="answer.can_comment and 'collapse' or ''"
 -                                    t-attf-data-target="#comment#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Comment
 -                                </a>
 -                            </li>
 -                            <li>
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/edit'"/>
 -                                    <t t-set="label" t-value="'Edit'"/>
 -                                    <t t-set="classes" t-value="'fa fa-edit'"/>
 -                                    <t t-set="karma" t-value="not answer.can_edit and answer.karma_edit or 0"/>
 -                                </t>
 -                            </li>
 -                            <li>
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/delete'"/>
 -                                    <t t-set="label" t-value="'Delete'"/>
 -                                    <t t-set="classes" t-value="'fa-trash-o'"/>
 -                                    <t t-set="karma" t-value="not answer.can_unlink and answer.karma_unlink or 0"/>
 -                                </t>
 -                            </li>
 -                            <li>
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/convert_to_comment'"/>
 -                                    <t t-set="label" t-value="'Convert as a comment'"/>
 -                                    <t t-set="classes" t-value="'fa-magic'"/>
 -                                    <t t-set="karma" t-value="not answer.can_comment_convert and answer.karma_comment_convert or 0"/>
 -                                </t>
 -                            </li>
 -                        </ul>
 -                        <img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{answer.create_uid.id}/avatar"/>
 -                        <div>
 -                            <a t-attf-href="/forum/#{ slug(forum) }/user/#{ answer.create_uid.id }"
 -                                t-field="answer.create_uid"
 -                                t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'
 -                                style="display: inline-block;"/>
 -                            <div t-field="answer.create_uid" t-field-options='{"widget": "contact", "badges": true, "fields": ["karma"]}'/>
 -                            <span class="text-muted">Answered on <span t-field="answer.create_date" t-field-options='{"format":"short"}'/></span>
 -                        </div>
 -                        <div class="visible-xs text-center">
 -                            <t t-call="website_forum.vote">
 -                                <t t-set="post" t-value="answer"/>
 -                            </t>
 -                            <div class="text-muted mt8">
 -                                <a t-attf-class="accept_answer fa fa-2x fa-check-circle no-decoration #{answer.is_correct and 'oe_answer_true' or 'oe_answer_false'} #{not answer.can_accept and 'karma_required' or ''}"
 -                                    t-attf-data-karma="#{answer.karma_accept}"
 -                                    t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct"/>
 -                            </div>
 -                        </div>
 -                    </div>
 -                    <t t-call="website_forum.post_comment">
 -                        <t t-set="object" t-value="answer"/>
 -                    </t>
 -                </div>
 +            </div>
 +            <t t-if="answer.type == 'question'" t-call="website_forum.post_comment">
 +                <t t-set="object" t-value="answer"/>
 +            </t>
 +            <div t-if="answer.type != 'question' and question.state != 'close' and question.active != False">
 +                <t t-call="website_forum.post_reply">
 +                    <t t-set="object" t-value="answer"/>
 +                </t>
 +            </div>
 +            <div t-foreach="answer.child_ids" t-as="child_answer" class="mt16 mb16">
 +                <t t-call="website_forum.post_answers">
 +                    <t t-set="answer" t-value="child_answer"/>
 +                </t>
              </div>
          </div>
 -        <div t-if="not question.uid_has_answered">
 -            <t t-call="website_forum.post_answer"/>
 -        </div>
 -        <div t-if="question.uid_has_answered" class="mb16">
 -            <a class="btn btn-primary" t-attf-href="/forum/#{slug(forum)}/question/#{slug(question)}/edit_answer">Edit Your Previous Answer</a>
 -            <span class="text-muted">(only one answer per question is allowed)</span>
 -        </div>
 -    </t>
 +    </div>
  </template>
  
  <!-- Utility template: Post a Comment -->
  
                      <span t-field="message.body"/>
                      <t t-set="required_karma" t-value="message.author_id.id == user.partner_id.id and object.forum_id.karma_comment_convert_own or object.forum_id.karma_comment_convert_all"/>
 -                    <t t-call="website_forum.link_button">
 -                        <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
 -                        <t t-set="label" t-value="'Convert as an answer'"/>
 -                        <t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
 -                        <t t-set="classes" t-value="'fa-magic pull-right'"/>
 +                    <t t-if="(object.parent_id and object.parent_id.state != 'close' and object.parent_id.active != False) or (not object.parent_id and object.state != 'close' and object.active != False)">
 +                        <t t-set="allow_post_comment" t-value="True" />
 +                    </t>
 +                    <t t-if="allow_post_comment">
 +                        <t t-call="website_forum.link_button" >
 +                            <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
 +                            <t t-set="label" t-value="'Convert as an answer'"/>
 +                            <t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
 +                            <t t-set="classes" t-value="'fa-magic pull-right'"/>
 +                        </t>
                      </t>
                      <a t-attf-href="/forum/#{slug(forum)}/partner/#{message.author_id.id}"
                          t-field="message.author_id" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'
          </p>
          <div class="row">
              <div class="col-sm-3 mt16" t-foreach="tags" t-as="tag">
 -                <a t-attf-href="/forum/#{ slug(forum) }/tag/#{ slug(tag) }/questions?{{ keep_query( filters='tag') }}" class="badge">
 +                <a t-attf-href="/forum/#{ slug(forum) }/tag/#{ slug(tag) }/questions?{{ keep_query( filters='tag') }}" class="label label-default">
                      <span t-field="tag.name"/>
                  </a>
                  <span>
@@@ -2,12 -2,6 +2,12 @@@
  <openerp>
      <data>
  
 +<template id="assets_editor" inherit_id="website.assets_editor" name="Email Designer">
 +    <xpath expr="." position="inside">
 +        <script type="text/javascript" src="/website_mail/static/src/js/website_email_designer.js"></script>
 +    </xpath>
 +</template>
 +
  <!-- Template Choice page -->
  <template id="email_designer" name="Email Designer">
      <t t-call="website.layout">
@@@ -30,7 -24,7 +30,7 @@@
                      </div>
                      <div t-foreach="templates" t-as="template" class="col-md-3 col-sm-4 text-center">
                          <div class="email_preview_border">
-                             <div t-esc="html_sanitize(template.body_html)" class="email_preview js_content"/>
+                             <div t-raw="html_sanitize(template.body_html)" class="email_preview js_content"/>
                          </div>
                          <h4 t-field="template.name"/>
                          <button class="btn btn-primary js_template_set">Select</button>
@@@ -38,7 -32,7 +38,7 @@@
                  </div>
              </div>
              <div id="email_designer" class="mb32" t-att-style="mode != 'email_designer' and 'display: none' or ''">
 -                <a class="mt16 btn btn-primary pull-right" 
 +                <a class="mt16 btn btn-primary pull-right" id="save_and_continue"
                    t-attf-href="/web#return_label=Website&amp;model=#{model}&amp;id=#{res_id}&amp;view_type=form">
                      Save and Continue
                  </a>
@@@ -37,14 -37,18 +37,18 @@@ class table_compute(object)
          for p in products:
              x = min(max(p.website_size_x, 1), PPR)
              y = min(max(p.website_size_y, 1), PPR)
-             if index>PPG:
+             if index>=PPG:
                  x = y = 1
  
              pos = minpos
              while not self._check_place(pos%PPR, pos/PPR, x, y):
                  pos += 1
-             if index>PPG and (pos/PPR)>maxy:
+             # if 21st products (index 20) and the last line is full (PPR products in it), break
+             # (pos + 1.0) / PPR is the line where the product would be inserted
+             # maxy is the number of existing lines
+             # + 1.0 is because pos begins at 0, thus pos 20 is actually the 21st block
+             # and to force python to not round the division operation
+             if index >= PPG and ((pos + 1.0) / PPR) > maxy:
                  break
  
              if x==1 and y==1:   # simple heuristic for CPU optimization
@@@ -168,11 -172,17 +172,11 @@@ class website_sale(http.Controller)
          else:
              pricelist = pool.get('product.pricelist').browse(cr, uid, context['pricelist'], context)
  
 -        product_obj = pool.get('product.template')
 -
          url = "/shop"
 -        product_count = product_obj.search_count(cr, uid, domain, context=context)
          if search:
              post["search"] = search
          if category:
              url = "/shop/category/%s" % slug(category)
 -        pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
 -        product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
 -        products = product_obj.browse(cr, uid, product_ids, context=context)
  
          style_obj = pool['product.style']
          style_ids = style_obj.search(cr, uid, [], context=context)
          categories = category_obj.browse(cr, uid, category_ids, context=context)
          categs = filter(lambda x: not x.parent_id, categories)
  
 +        domain += [('public_categ_ids', 'in', category_ids)]
 +        product_obj = pool.get('product.template')
 +
 +        product_count = product_obj.search_count(cr, uid, domain, context=context)
 +        pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
 +        product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
 +        products = product_obj.browse(cr, uid, product_ids, context=context)
 +
          attributes_obj = request.registry['product.attribute']
          attributes_ids = attributes_obj.search(cr, uid, [], context=context)
          attributes = attributes_obj.browse(cr, uid, attributes_ids, context=context)
                  'sale_order_id': order.id,
              }, context=context)
              request.session['sale_transaction_id'] = tx_id
 +            tx = transaction_obj.browse(cr, SUPERUSER_ID, tx_id, context=context)
  
          # update quotation
          request.registry['sale.order'].write(
                  'payment_tx_id': request.session['sale_transaction_id']
              }, context=context)
  
 +        # confirm the quotation
 +        if tx.acquirer_id.auto_confirm == 'at_pay_now':
 +            request.registry['sale.order'].action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
 +
          return tx_id
  
      @http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
                            t-attf-class="oe_product oe_grid oe-height-#{td_product['y']*2} #{ td_product['class'] }">
  
                            <div class="oe_product_cart" t-att-data-publish="product.website_published and 'on' or 'off'">
 -
 -                            <div class="css_options" t-ignore="true" groups="base.group_website_publisher">
 -                              <div t-attf-class="dropdown js_options" t-att-data-id="product.id">
 -                                <button class="btn btn-default" t-att-id="'dopprod-%s' % product.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></button>
 -                                <ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % product.id">
 -                                  <li class='dropdown-submenu'>
 -                                    <a tabindex="-1" href="#">Size</a>
 -                                    <ul class="dropdown-menu" name="size">
 -                                      <li><a href="#">
 -                                        <table>
 -                                          <tr>
 -                                            <td class="selected"></td>
 -                                            <td t-att-class="product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                          <tr>
 -                                            <td t-att-class="product.website_size_y > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 1 and product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 1 and product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 1 and product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                          <tr>
 -                                            <td t-att-class="product.website_size_y > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 2 and product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 2 and product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 2 and product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                          <tr>
 -                                            <td t-att-class="product.website_size_y > 3 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 3 and product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 3 and product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 3 and product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                        </table>
 -                                      </a></li>
 -                                    </ul>
 -                                  </li>
 -                                  <li class='dropdown-submenu'>
 -                                    <a tabindex="-1" href="#">Styles</a>
 -                                    <ul class="dropdown-menu" name="style">
 -                                      <t t-foreach="styles" t-as="style">
 -                                        <li t-att-class="style_in_product(style, product) and 'active' or ''"><a href="#" t-att-data-id="style.id" t-att-data-class="style.html_class"><t t-esc="style.name"/></a></li>
 -                                      </t>
 -                                    </ul>
 -                                  </li>
 -                                  <li class='dropdown-submenu'>
 -                                      <a tabindex="-1" href="#">Promote</a>
 -                                      <ul class="dropdown-menu" name="sequence">
 -                                          <li><a href="#" class="js_go_to_top">Push to top</a></li>
 -                                          <li><a href="#" class="js_go_up">Push up</a>
 -                                          </li>
 -                                          <li><a href="#" class="js_go_down">Push down</a></li>
 -                                          <li><a href="#" class="js_go_to_bottom">Push to bottom</a></li>
 -                                      </ul>
 -                                  </li>
 -                                </ul>
 -                              </div>
 -                            </div>
                              <t t-set="product_image_big" t-value="td_product['x']+td_product['y'] > 2"/>
                              <t t-call="website_sale.products_item"/>
                            </div>
      <input type="hidden" t-if="len(product.product_variant_ids) == 1" name="product_id" t-att-value="product.product_variant_ids[0].id"/>
      <t t-if="len(product.product_variant_ids) &gt; 1">
        <label label-default="label-default" class="radio" t-foreach="product.product_variant_ids" t-as="variant_id">
-         <input type="radio" name="product_id" class="js_product_change" t-att-value="variant_id.id" t-att-data-lst_price="variant_id.lst_price" t-att-data-price="variant_id.price"/>
+         <input type="radio" name="product_id" class="js_product_change" t-att-checked="'checked' if variant_id_index == 0 else ''" t-att-value="variant_id.id" t-att-data-lst_price="variant_id.lst_price" t-att-data-price="variant_id.price"/>
          <span t-esc="variant_id.name_get()[0][1]"/>
          <span class="badge" t-if="variant_id.price_extra">
            <t t-esc="variant_id.price_extra > 0 and '+' or ''"/><span t-field="variant_id.price_extra" style="white-space: nowrap;" t-field-options='{
                    <h2>Thank you for your order.</h2>
                    <div class="oe_website_sale_tx_status" t-att-data-order-id="order.id">
                    </div>
 +                  <h3 class="mt32"><strong>Order Details:</strong></h3>
 +                  <table class="table">
 +                      <thead>
 +                          <tr>
 +                              <th>Products</th>
 +                              <th>Quantity</th>
 +                              <th class="text-right" width="100">Unit Price</th>
 +                              <th class="text-right" width="100">Subtotal</th>
 +                          </tr>
 +                      </thead>
 +                      <tbody>
 +                          <tr t-foreach="order.order_line" t-as="line">
 +                              <td>
 +                                  <div>
 +                                      <a t-attf-href="/shop/product/#{ slug(line.product_id.product_tmpl_id) }">
 +                                          <strong t-esc="line.product_id.name_get()[0][1]"/>
 +                                      </a>
 +                                  </div>
 +                                  <div class="text-muted" t-field="line.name"/>
 +                              </td>
 +                              <td>
 +                                  <div id="quote_qty">
 +                                      <span t-field="line.product_uom_qty"/>
 +                                      <span t-field="line.product_uom"/>
 +                                  </div>
 +                              </td>
 +                              <td>
 +                                  <strong class="text-right">
 +                                      <div t-field="line.price_unit"
 +                                          t-field-options='{"widget": "monetary", "display_currency": "order.pricelist_id.currency_id"}'/>
 +                                  </strong>
 +                              </td>
 +                              <td>
 +                                  <div class="text-right"
 +                                      t-field="line.price_subtotal"
 +                                      t-field-options='{"widget": "monetary", "display_currency": "order.pricelist_id.currency_id"}'/>
 +                              </td>
 +                          </tr>
 +                          <tr>
 +                              <td></td><td></td>
 +                              <td class="text-right"><strong>Total:</strong></td>
 +                              <td class="text-right">
 +                                  <strong t-field="order.amount_total"
 +                                      t-field-options='{"widget": "monetary", "display_currency": "order.pricelist_id.currency_id"}'/>
 +                              </td>
 +                          </tr>
 +                      </tbody>
 +                  </table>
                    <div class="clearfix"/>
                    <div class="oe_structure"/>
                </div>
diff --combined openerp/models.py
@@@ -178,7 -178,12 +178,12 @@@ def get_pg_type(f, type_override=None)
      if field_type in FIELDS_TO_PGTYPES:
          pg_type =  (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type])
      elif issubclass(field_type, fields.float):
-         if f.digits:
+         # Explicit support for "falsy" digits (0, False) to indicate a
+         # NUMERIC field with no fixed precision. The values will be saved
+         # in the database with all significant digits.
+         # FLOAT8 type is still the default when there is no precision because
+         # it is faster for most operations (sums, etc.)
+         if f.digits is not None:
              pg_type = ('numeric', 'NUMERIC')
          else:
              pg_type = ('float8', 'DOUBLE PRECISION')
@@@ -829,7 -834,7 +834,7 @@@ class BaseModel(object)
          # check defaults
          for k in cls._defaults:
              assert k in cls._fields, \
 -                "Model %s has a default for nonexiting field %s" % (cls._name, k)
 +                "Model %s has a default for non-existing field %s" % (cls._name, k)
  
          # restart columns
          for column in cls._columns.itervalues():
                  _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
                                self._table, column['attname'])
  
 -    def _save_constraint(self, cr, constraint_name, type):
 +    def _save_constraint(self, cr, constraint_name, type, definition):
          """
          Record the creation of a constraint for this model, to make it possible
          to delete it later when the module is uninstalled. Type can be either
              return
          assert type in ('f', 'u')
          cr.execute("""
 -            SELECT 1 FROM ir_model_constraint, ir_module_module
 +            SELECT type, definition FROM ir_model_constraint, ir_module_module
              WHERE ir_model_constraint.module=ir_module_module.id
                  AND ir_model_constraint.name=%s
                  AND ir_module_module.name=%s
              """, (constraint_name, self._module))
 -        if not cr.rowcount:
 +        constraints = cr.dictfetchone()
 +        if not constraints:
              cr.execute("""
                  INSERT INTO ir_model_constraint
 -                    (name, date_init, date_update, module, model, type)
 +                    (name, date_init, date_update, module, model, type, definition)
                  VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
                      (SELECT id FROM ir_module_module WHERE name=%s),
 -                    (SELECT id FROM ir_model WHERE model=%s), %s)""",
 -                    (constraint_name, self._module, self._name, type))
 +                    (SELECT id FROM ir_model WHERE model=%s), %s, %s)""",
 +                    (constraint_name, self._module, self._name, type, definition))
 +        elif constraints['type'] != type or (definition and constraints['definition'] != definition):
 +            cr.execute("""
 +                UPDATE ir_model_constraint
 +                SET date_update=now() AT TIME ZONE 'UTC', type=%s, definition=%s
 +                WHERE name=%s AND module = (SELECT id FROM ir_module_module WHERE name=%s)""",
 +                    (type, definition, constraint_name, self._module))
  
      def _save_relation_table(self, cr, relation_table):
          """
          """ Create the foreign keys recorded by _auto_init. """
          for t, k, r, d in self._foreign_keys:
              cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
 -            self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f')
 +            self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f', False)
          cr.commit()
          del self._foreign_keys
  
          for (key, con, _) in self._sql_constraints:
              conname = '%s_%s' % (self._table, key)
  
 -            self._save_constraint(cr, conname, 'u')
 -            cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
 -            existing_constraints = cr.dictfetchall()
 +            # using 1 to get result if no imc but one pgc
 +            cr.execute("""SELECT definition, 1
 +                          FROM ir_model_constraint imc
 +                          RIGHT JOIN pg_constraint pgc
 +                          ON (pgc.conname = imc.name)
 +                          WHERE pgc.conname=%s
 +                          """, (conname, ))
 +            existing_constraints = cr.dictfetchone()
              sql_actions = {
                  'drop': {
                      'execute': False,
                  # constraint does not exists:
                  sql_actions['add']['execute'] = True
                  sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
 -            elif unify_cons_text(con) not in [unify_cons_text(item['condef']) for item in existing_constraints]:
 +            elif unify_cons_text(con) != existing_constraints['definition']:
                  # constraint exists but its definition has changed:
                  sql_actions['drop']['execute'] = True
 -                sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
 +                sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints['definition'] or '', )
                  sql_actions['add']['execute'] = True
                  sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
  
              # we need to add the constraint:
 +            self._save_constraint(cr, conname, 'u', unify_cons_text(con))
              sql_actions = [item for item in sql_actions.values()]
              sql_actions.sort(key=lambda x: x['order'])
              for sql_action in [action for action in sql_actions if action['execute']]: