[MERGE] forward port of branch 8.0 up to 591e329
authorChristophe Simonis <chs@odoo.com>
Tue, 2 Dec 2014 15:18:08 +0000 (16:18 +0100)
committerChristophe Simonis <chs@odoo.com>
Tue, 2 Dec 2014 15:18:08 +0000 (16:18 +0100)
67 files changed:
1  2 
addons/account/account_bank_statement.py
addons/account/account_invoice.py
addons/analytic_user_function/analytic_user_function_view.xml
addons/calendar/calendar.py
addons/crm/crm.py
addons/crm/crm_lead_view.xml
addons/crm/report/crm_lead_report_view.xml
addons/crm/report/crm_opportunity_report_view.xml
addons/im_chat/im_chat.py
addons/mass_mailing/models/mass_mailing.py
addons/mrp/mrp_view.xml
addons/mrp/security/ir.model.access.csv
addons/payment/models/payment_acquirer.py
addons/payment/views/payment_acquirer.xml
addons/point_of_sale/point_of_sale_view.xml
addons/point_of_sale/static/src/js/models.js
addons/point_of_sale/static/src/js/screens.js
addons/point_of_sale/static/src/js/widgets.js
addons/product/pricelist_view.xml
addons/product/product.py
addons/product/product_view.xml
addons/project_timesheet/project_timesheet.py
addons/purchase/purchase.py
addons/purchase/purchase_view.xml
addons/stock/product.py
addons/stock/stock_demo.yml
addons/stock/stock_view.xml
addons/web/controllers/main.py
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/chrome.js
addons/web/static/src/js/search.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/view_list.js
addons/web/views/webclient_templates.xml
addons/web_calendar/static/src/js/web_calendar.js
addons/web_graph/static/src/js/graph_widget.js
addons/website/models/res_config.py
addons/website/models/website.py
addons/website/static/src/css/website.css
addons/website/static/src/css/website.sass
addons/website/static/src/js/website.editor.js
addons/website/views/res_config.xml
addons/website_blog/data/website_blog_data.xml
addons/website_blog/models/website_blog.py
addons/website_blog/security/ir.model.access.csv
addons/website_blog/views/website_blog_views.xml
addons/website_crm/controllers/main.py
addons/website_forum/controllers/main.py
addons/website_forum/models/forum.py
addons/website_forum/static/src/js/website_forum.js
addons/website_forum/views/website_forum.xml
addons/website_mail/views/website_email_designer.xml
addons/website_sale/controllers/main.py
addons/website_sale/models/sale_order.py
doc/conf.py
doc/reference.rst
openerp/addons/base/ir/ir_qweb.py
openerp/addons/base/ir/ir_ui_view.py
openerp/addons/base/res/res_partner.py
openerp/addons/base/res/res_partner_view.xml
openerp/addons/base/res/res_users.py
openerp/models.py
openerp/service/server.py
openerp/tools/convert.py
openerp/tools/misc.py
setup/win32/setup.nsi

@@@ -23,6 -23,7 +23,7 @@@ from openerp.osv import fields, os
  from openerp.tools.translate import _
  import openerp.addons.decimal_precision as dp
  from openerp.report import report_sxw
+ from openerp.tools import float_compare, float_round
  
  import time
  
@@@ -547,34 -548,51 +548,51 @@@ class account_bank_statement_line(osv.o
                      mv_line['has_no_partner'] = False
                  return [mv_line]
  
-         # If there is no identified partner or structured communication, don't look further
-         if not st_line.partner_id.id:
-             return []
-         # Look for a move line whose amount matches the statement line's amount
-         company_currency = st_line.journal_id.company_id.currency_id.id
-         statement_currency = st_line.journal_id.currency.id or company_currency
-         sign = 1
-         if statement_currency == company_currency:
-             amount_field = 'credit'
-             if st_line.amount > 0:
-                 amount_field = 'debit'
-             else:
+         # How to compare statement line amount and move lines amount
+         precision_digits = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
+         currency_id = st_line.currency_id.id or st_line.journal_id.currency.id
+         # NB : amount can't be == 0 ; so float precision is not an issue for amount > 0 or amount < 0
+         amount = st_line.amount_currency or st_line.amount
+         domain = [('reconcile_partial_id', '=', False)]
+         if currency_id:
+             domain += [('currency_id', '=', currency_id)]
+         sign = 1 # correct the fact that st_line.amount is signed and debit/credit is not
+         amount_field = 'debit'
+         if currency_id == False:
+             if amount < 0:
+                 amount_field = 'credit'
                  sign = -1
          else:
              amount_field = 'amount_currency'
-             if st_line.amount < 0:
-                 sign = -1
-         if st_line.amount_currency:
-             amount = st_line.amount_currency
-         else:
-             amount = st_line.amount
  
-         match_id = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, offset=0, limit=1, additional_domain=[(amount_field, '=', (sign * amount))])
+         # Look for a matching amount
+         domain_exact_amount = domain + [(amount_field, '=', float_round(sign * amount, precision_digits=precision_digits))]
+         match_id = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, offset=0, limit=1, additional_domain=domain_exact_amount)
          if match_id:
-             return [match_id[0]]
+             return match_id
  
-         return []
+         if not st_line.partner_id.id:
+             return []
+         # Look for a set of move line whose amount is <= to the line's amount
+         if amount > 0: # Make sure we can't mix receivable and payable
+             domain += [('account_id.type', '=', 'receivable')]
+         else:
+             domain += [('account_id.type', '=', 'payable')]
+         if amount_field == 'amount_currency' and amount < 0:
+             domain += [(amount_field, '<', 0), (amount_field, '>', (sign * amount))]
+         else:
+             domain += [(amount_field, '>', 0), (amount_field, '<', (sign * amount))]
+         mv_lines = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, limit=5, additional_domain=domain)
+         ret = []
+         total = 0
+         for line in mv_lines:
+             total += abs(line['debit'] - line['credit'])
+             if float_compare(total, abs(amount), precision_digits=precision_digits) != 1:
+                 ret.append(line)
+             else:
+                 break
+         return ret
  
      def get_move_lines_for_reconciliation_by_statement_line_id(self, cr, uid, st_line_id, excluded_ids=None, str=False, offset=0, limit=None, count=False, additional_domain=None, context=None):
          """ Bridge between the web client reconciliation widget and get_move_lines_for_reconciliation (which expects a browse record) """
          if additional_domain is None:
              additional_domain = []
          # Make domain
-         domain = additional_domain + [('reconcile_id', '=', False),
-                                       ('state', '=', 'valid'),
-                                       ('account_id.reconcile', '=', True)]
+         domain = additional_domain + [
+             ('reconcile_id', '=', False),
+             ('state', '=', 'valid'),
+             ('account_id.reconcile', '=', True)
+         ]
          if st_line.partner_id.id:
              domain += [('partner_id', '=', st_line.partner_id.id)]
          if excluded_ids:
              domain.append(('id', 'not in', excluded_ids))
          if str:
-             domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)]
+             domain += [
+                 '|', ('move_id.name', 'ilike', str),
+                 '|', ('move_id.ref', 'ilike', str),
+                 ('date_maturity', 'like', str),
+             ]
              if not st_line.partner_id.id:
                  domain.insert(-1, '|', )
                  domain.append(('partner_id.name', 'ilike', str))
          'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."),
      }
      _defaults = {
 -        'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'),
          'date': lambda self,cr,uid,context={}: context.get('date', fields.date.context_today(self,cr,uid,context=context)),
      }
  
@@@ -834,18 -859,17 +858,18 @@@ class account_statement_operation_templ
      _description = "Preset for the lines that can be created in a bank statement reconciliation"
      _columns = {
          'name': fields.char('Button Label', required=True),
-         'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type','not in',('view','closed','consolidation'))]),
+         'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type', 'not in', ('view', 'closed', 'consolidation'))]),
 -        'label': fields.char('Label'),
 -        'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')],
 -                                   'Amount type', required=True),
 -        'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance').", required=True),
 -        'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use', 'in', ['purchase', 'all']), ('parent_id', '=', False)]),
 +        'label': fields.char('Journal Item Label'),
 +        'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')], 'Amount type', required=True),
 +        'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), required=True, help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance')."),
 +        'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use','in',('purchase','all')), ('parent_id','=',False)]),
          'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='set null', domain=[('type','!=','view'), ('state','not in',('close','cancelled'))]),
 +        'company_id': fields.many2one('res.company', 'Company', required=True),
      }
      _defaults = {
          'amount_type': 'percentage_of_balance',
 -        'amount': 100.0
 +        'amount': 100.0,
 +        'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
      }
  
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -87,7 -87,8 +87,8 @@@ class account_invoice(models.Model)
      @api.returns('account.analytic.journal', lambda r: r.id)
      def _get_journal_analytic(self, inv_type):
          """ Return the analytic journal corresponding to the given invoice type. """
-         journal_type = TYPE2JOURNAL.get(inv_type, 'sale')
+         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
+         journal_type = type2journal.get(inv_type, 'sale')
          journal = self.env['account.analytic.journal'].search([('type', '=', journal_type)], limit=1)
          if not journal:
              raise except_orm(_('No Analytic Journal!'),
                      if line.value == 'fixed':
                          total_fixed += line.value_amount
                      if line.value == 'procent':
 -                        total_percent += line.value_amount
 +                        total_percent += (line.value_amount/100.0)
                  total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0)
                  if (total_fixed + total_percent) > 100:
                      raise except_orm(_('Error!'), _("Cannot create the invoice.\nThe related payment term is probably misconfigured as it gives a computed amount greater than the total invoiced amount. In order to avoid rounding issues, the latest line of your payment term must be of type 'balance'."))
@@@ -1619,7 -1620,7 +1620,7 @@@ class res_partner(models.Model)
      _inherit = 'res.partner'
  
      invoice_ids = fields.One2many('account.invoice', 'partner_id', string='Invoices',
-         readonly=True)
+         readonly=True, copy=False)
  
      def _find_accounting_partner(self, partner):
          '''
@@@ -65,8 -65,8 +65,8 @@@
              <field name="priority" eval="18"/>
              <field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
              <field name="arch" type="xml">
-                 <xpath expr="//field[@name='timesheet_ids']/tree/field[@name='account_id']" position="replace">
-                     <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)" context="{'default_invoice_on_timesheets': 1}"/>
+                 <xpath expr="//field[@name='timesheet_ids']/tree/field[@name='account_id']" position="attributes">
+                     <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
                  </xpath>
              </field>
          </record>
@@@ -79,7 -79,7 +79,7 @@@
              <field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
              <field name="arch" type="xml">
                  <xpath expr="//field[@name='timesheet_ids']/form/field[@name='account_id']" position="replace">
 -                    <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)" context="{'default_use_timesheets': 1}"/>
 +                    <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)" context="{'default_invoice_on_timesheets': 1}"/>
                  </xpath>
              </field>
          </record>
@@@ -6,6 -6,7 +6,7 @@@ import tim
  import openerp
  import openerp.service.report
  import uuid
+ import collections
  from werkzeug.exceptions import BadRequest
  from datetime import datetime, timedelta
  from dateutil import parser
@@@ -737,8 -738,10 +738,10 @@@ class calendar_event(osv.Model)
              ids = res_lang.search(request.cr, uid, [("code", "=", lang)])
              if ids:
                  lang_params = res_lang.read(request.cr, uid, ids[0], ["date_format", "time_format"])
-         format_date = lang_params.get("date_format", '%B-%d-%Y')
-         format_time = lang_params.get("time_format", '%I-%M %p')
+         # formats will be used for str{f,p}time() which do not support unicode in Python 2, coerce to str
+         format_date = lang_params.get("date_format", '%B-%d-%Y').encode('utf-8')
+         format_time = lang_params.get("time_format", '%I-%M %p').encode('utf-8')
          return (format_date, format_time)
  
      def get_display_time_tz(self, cr, uid, ids, tz=False, context=None):
          if not tz:  # tz can have a value False, so dont do it in the default value of get !
              context['tz'] = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
              tz = context['tz']
+         tz = tools.ustr(tz).encode('utf-8') # make safe for str{p,f}time()
  
          format_date, format_time = self.get_date_formats(cr, uid, context=context)
          date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
          '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)]}),
  
                      name_get = browse_event[ord].name_get()
                      if len(name_get) and len(name_get[0]) >= 2:
                          sort_fields[ord] = name_get[0][1]
+         if r_date:
+             sort_fields['sort_start'] = r_date.strftime("%Y%m%d%H%M%S")
+         else:
+             sort_fields['sort_start'] = browse_event['display_start'].replace(' ', '').replace('-', '')
          return sort_fields
  
      def get_recurrent_ids(self, cr, uid, event_id, domain, order=None, context=None):
                  result_data.append(self.get_search_fields(ev, order_fields, r_date=r_date))
  
          if order_fields:
+             uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values()
              def comparer(left, right):
                  for fn, mult in comparers:
                      result = cmp(fn(left), fn(right))
                  return 0
  
              sort_params = [key.split()[0] if key[-4:].lower() != 'desc' else '-%s' % key.split()[0] for key in (order or self._order).split(',')]
+             sort_params = uniq([comp if comp not in ['start', 'start_date', 'start_datetime'] else 'sort_start' for comp in sort_params])
+             sort_params = uniq([comp if comp not in ['-start', '-start_date', '-start_datetime'] else '-sort_start' for comp in sort_params])
              comparers = [((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
              ids = [r['id'] for r in sorted(result_data, cmp=comparer)]
  
diff --combined addons/crm/crm.py
@@@ -23,10 -23,11 +23,10 @@@ from openerp.osv import osv, field
  from openerp.http import request
  
  AVAILABLE_PRIORITIES = [
 -    ('0', 'Very Low'),
 +    ('0', 'Normal'),
      ('1', 'Low'),
 -    ('2', 'Normal'),
 -    ('3', 'High'),
 -    ('4', 'Very High'),
 +    ('2', 'High'),
 +    ('3', 'Very High'),
  ]
  
  
@@@ -51,7 -52,7 +51,7 @@@ class crm_tracking_campaign(osv.Model)
      _rec_name = "name"
      _columns = {
          'name': fields.char('Campaign Name', required=True, translate=True),
 -        'section_id': fields.many2one('crm.case.section', 'Sales Team'),
 +        'team_id': fields.many2one('crm.team', 'Sales Team', oldname='section_id'),
      }
  
  
@@@ -69,10 -70,10 +69,10 @@@ class crm_tracking_mixin(osv.AbstractMo
      _name = 'crm.tracking.mixin'
  
      _columns = {
 -        'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign',  # old domain ="['|',('section_id','=',section_id),('section_id','=',False)]"
 +        'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign',  # old domain ="['|',('team_id','=',team_id),('team_id','=',False)]"
                                         help="This is a name that helps you keep track of your different campaign efforts Ex: Fall_Drive, Christmas_Special"),
          'source_id': fields.many2one('crm.tracking.source', 'Source', help="This is the source of the link Ex: Search Engine, another domain, or name of email list"),
-         'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad"),
+         'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad", oldname='channel_id'),
      }
  
      def tracking_fields(self):
      }
  
  
 -class crm_case_stage(osv.osv):
 +class crm_stage(osv.Model):
      """ Model for case stages. This models the main stages of a document
          management flow. Main CRM objects (leads, opportunities, project
          issues, ...) will now use only stages, instead of state and stages.
          Stages are for example used to display the kanban view of records.
      """
 -    _name = "crm.case.stage"
 +    _name = "crm.stage"
      _description = "Stage of case"
      _rec_name = 'name'
      _order = "sequence"
          'probability': fields.float('Probability (%)', required=True, help="This percentage depicts the default/average probability of the Case for this stage to be a success"),
          'on_change': fields.boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity."),
          'requirements': fields.text('Requirements'),
 -        'section_ids': fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', string='Sections',
 -                                        help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
 +        'team_ids': fields.many2many('crm.team', 'crm_team_stage_rel', 'stage_id', 'team_id', string='Teams',
 +                        help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
          'case_default': fields.boolean('Default to New Sales Team',
 -                                       help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
 +                        help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
 +        'legend_priority': fields.text(
 +            'Priority Management Explanation', translate=True,
 +            help='Explanation text to help users using the star and priority mechanism on stages or issues that are in this stage.'),
          'fold': fields.boolean('Folded in Kanban View',
                                 help='This stage is folded in the kanban view when'
                                 'there are no records in that stage to display.'),
          'case_default': True,
      }
  
 -
 -class crm_case_categ(osv.osv):
 -    """ Category of Case """
 -    _name = "crm.case.categ"
 -    _description = "Category of Case"
 -    _columns = {
 -        'name': fields.char('Name', required=True, translate=True),
 -        'section_id': fields.many2one('crm.case.section', 'Sales Team'),
 -        'object_id': fields.many2one('ir.model', 'Object Name'),
 -    }
 -
 -    def _find_object_id(self, cr, uid, context=None):
 -        """Finds id for case object"""
 -        context = context or {}
 -        object_id = context.get('object_id', False)
 -        ids = self.pool.get('ir.model').search(cr, uid, ['|', ('id', '=', object_id), ('model', '=', context.get('object_name', False))])
 -        return ids and ids[0] or False
 -    _defaults = {
 -        'object_id': _find_object_id
 -    }
 -
 -
 -class crm_payment_mode(osv.osv):
 -    """ Payment Mode for Fund """
 -    _name = "crm.payment.mode"
 -    _description = "CRM Payment Mode"
 -    _columns = {
 -        'name': fields.char('Name', required=True),
 -        'section_id': fields.many2one('crm.case.section', 'Sales Team'),
 -    }
 -
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -7,7 -7,7 +7,7 @@@
          <!-- Stage Search view -->
          <record id="crm_lead_stage_search" model="ir.ui.view">
              <field name="name">Stage - Search</field>
 -            <field name="model">crm.case.stage</field>
 +            <field name="model">crm.stage</field>
              <field name="arch" type="xml">
                  <search string="Stage Search">
                      <field name="name"/>
@@@ -21,9 -21,9 +21,9 @@@
          <!-- Stage Form view -->
          <record id="crm_lead_stage_act" model="ir.actions.act_window">
              <field name="name">Stages</field>
 -            <field name="res_model">crm.case.stage</field>
 +            <field name="res_model">crm.stage</field>
              <field name="view_type">form</field>
 -            <field name="view_id" ref="crm.crm_case_stage_tree"/>
 +            <field name="view_id" ref="crm.crm_stage_tree"/>
              <field name="help" type="html">
                <p class="oe_view_nocontent_create">
                  Click to set a new stage in your lead/opportunity pipeline.
              LEADS/OPPORTUNITIES CATEGORIES
          -->
  
 -        <!-- Categories Form View -->
 -        <record id="crm_lead_categ_action" model="ir.actions.act_window">
 +        <record id="crm_lead_tag_form" model="ir.ui.view">
 +            <field name="name">crm.lead.tag.form</field>
 +            <field name="model">crm.lead.tag</field>
 +            <field name="arch" type="xml">
 +                <form string="Sales Tags">
 +                    <group>
 +                        <field name="name"/>
 +                        <field name="team_id" groups="base.group_multi_salesteams"/>
 +                    </group>
 +                </form>
 +            </field>
 +        </record>
 +
 +        <record id="crm_lead_tag_tree" model="ir.ui.view">
 +            <field name="name">crm.lead.tag.tree</field>
 +            <field name="model">crm.lead.tag</field>
 +            <field name="arch" type="xml">
 +                <tree string="Sales Tags">
 +                    <field name="name"/>
 +                    <field name="team_id" groups="base.group_multi_salesteams"/>
 +                </tree>
 +            </field>
 +        </record>
 +
 +        <record id="crm_lead_tag_action" model="ir.actions.act_window">
              <field name="name">Sales Tags</field>
 -            <field name="res_model">crm.case.categ</field>
 +            <field name="res_model">crm.lead.tag</field>
              <field name="view_type">form</field>
 -            <field name="view_id" ref="crm_case_categ_tree-view"/>
 -            <field name="context" eval="{'object_id': ref('model_crm_lead')}"/>
 -            <field name="domain">[('object_id.model', '=', 'crm.lead')]</field>
 +            <field name="view_id" ref="crm_lead_tag_tree"/>
              <field name="help" type="html">
                <p class="oe_view_nocontent_create">
                  Click to define a new sales tag.
@@@ -81,8 -60,8 +81,8 @@@
              </field>
          </record>
  
 -        <menuitem action="crm_lead_categ_action"
 -            id="menu_crm_lead_categ" name="Sales Tags"
 +        <menuitem action="crm_lead_tag_action"
 +            id="menu_crm_lead_categ" name="Tags"
              parent="base.menu_crm_config_lead" sequence="1" groups="base.group_no_one"/>
  
          <!--
                      <button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action"
                              help="Convert to Opportunity" class="oe_highlight"/>
                      <field name="stage_id" widget="statusbar" clickable="True"
 -                            domain="['&amp;', '|', ('case_default', '=', True), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"
 +                            domain="['&amp;', '|', ('case_default', '=', True), ('team_ids', '=', team_id), '|', ('type', '=', type), ('type', '=', 'both')]"
                              options="{'fold_field': 'fold'}"
                              on_change="onchange_stage_id(stage_id)"/>
                  </header>
                      <div class="oe_right oe_button_box" name="buttons">
                          <button class="oe_inline oe_stat_button" type="action"
                              context="{'default_opportunity_id': active_id, 'search_default_opportunity_id': active_id, 'default_partner_id': partner_id, 'default_duration': 1.0}"
 -                            name="%(crm.crm_case_categ_phone_incoming0)d" icon="fa-phone">
 -                            <div>Schedule/Log<br/>Calls</div>
 +                            name="%(crm.crm_phonecall_category_phone_incoming0)d" icon="fa-phone">
 +                            <div>Calls</div>
                          </button>
                      </div>
                      <div class="oe_title">
                              <field name="partner_name" string="Company Name"/>
                              <!-- Preload all the partner's information -->
                              <field name="partner_id" string="Customer"
 -                                on_change="on_change_partner_id(partner_id)"
                                  options='{"create_name_field": "name"}'
 -                                context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_section_id': section_id}"/>
 +                                context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id}"/>
                              <label for="street" string="Address"/>
                              <div>
                                  <field name="street" placeholder="Street..."/>
                          <group>
                              <field name="user_id" on_change="on_change_user(user_id, context)"
                                  context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_sale_salesman_all_leads'] }"/>
 -                            <label for="section_id" groups="base.group_multi_salesteams"/>
 +                            <label for="team_id" groups="base.group_multi_salesteams"/>
                              <div groups="base.group_multi_salesteams">
 -                                <field name="section_id"/>
 +                                <field name="team_id"/>
                                  <button name="case_escalate" string="Escalate"
                                          type="object" class="oe_link"
 -                                        attrs="{'invisible': ['|', ('section_id','=',False), ('probability', '=', 100)]}"/>
 +                                        attrs="{'invisible': ['|', ('team_id','=',False), ('probability', '=', 100)]}"/>
                              </div>
                              <field name="type" invisible="1"/>
                          </group>
                          <group>
                              <field name="priority" widget="priority"/>
 -                            <field name="categ_ids"
 -                                widget="many2many_tags"
 -                                domain="[('object_id.model','=','crm.lead')]"
 -                                context="{'object_name': 'crm.lead'}"
 -                            />
 +                            <field name="tag_ids" widget="many2many_tags"/>
                          </group>
                      </group>
                      <notebook colspan="4">
                      <field name="stage_id"/>
                      <field name="user_id" invisible="1"/>
                      <field name="partner_id" invisible="1"/>
 -                    <field name="section_id" groups="base.group_multi_salesteams"/>
 +                    <field name="team_id" groups="base.group_multi_salesteams"/>
                      <field name="probability" invisible="1"/>
                      <field name="campaign_id" invisible="1"/>
                      <field name="referred" invisible="1"/>
              <field name="model">crm.lead</field>
              <field name="arch" type="xml">
                  <kanban default_group_by="stage_id">
 -                    <field name="stage_id"/>
 +                    <field name="stage_id" options='{"group_by_tooltip": {"requirements": "Description", "legend_priority": "Use of stars"}}'/>
                      <field name="color"/>
                      <field name="priority"/>
                      <field name="planned_revenue" sum="Expected Revenues"/>
                      <field name="partner_address_email"/>
                      <field name="message_summary"/>
                      <field name="message_unread"/>
 +                    <field name="tag_ids"/>
                      <templates>
                          <field name="date_deadline"/>
                          <t t-name="kanban-box">
                                      <ul class="oe_dropdown_menu">
                                          <t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
                                          <t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
 -                                        <li><a name="%(mail.action_email_compose_message_wizard)d" type="action">Send Email</a></li>
 -                                        <li><a type="action" name="%(crm.crm_case_categ_phone_incoming0)d" >Calls</a></li>
 +                                        <li><a name="%(mail.action_email_compose_message_wizard)d" type="action" >Send Email</a></li>
 +                                        <li><a type="action" name="%(crm.crm_phonecall_category_phone_incoming0)d">Calls</a></li>
                                          <li><a name="action_schedule_meeting" type="object">Schedule Meeting</a></li>
                                          <li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
                                      </ul>
                                  </div>
                                  <div class="oe_kanban_content">
                                      <div>
 -                                        <b><field name="name"/></b>
 +                                        <strong><field name="name"/></strong>
                                          <t t-if="record.planned_revenue.raw_value">
                                              - <b><t t-esc="record.planned_revenue.value"/>
                                              <field name="company_currency"/></b>
                                      <div>
                                          <field name="partner_id"/>
                                      </div>
 -                                    <div style="padding-left: 0.5em">
 +                                    <div class="oe_deadline">
                                          <t t-if="record.date_deadline.raw_value and record.date_deadline.raw_value lt (new Date())" t-set="red">oe_kanban_text_red</t>
                                          <span t-attf-class="#{red || ''}"><field name="date_action"/></span>
                                          <t t-if="record.date_action.raw_value"> : </t>
                                          <field name="title_action"/>
                                      </div>
 +                                    <div class="oe_kanban_footer_left">
 +                                        <t t-raw="record.message_summary.raw_value"/>
 +                                        <field name="tag_ids"/>
 +                                    </div>
                                      <div class="oe_kanban_bottom_right">
                                          <img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
                                          <div class="pull-left" groups="base.group_user">
                                              <field name="priority" widget="priority"/>
                                          </div>
                                      </div>
 -                                    <div class="oe_kanban_footer_left">
 -                                        <t t-raw="record.message_summary.raw_value"/>
 -                                    </div>
                                  </div>
                                  <div class="oe_clear"></div>
                              </div>
              <field name="arch" type="xml">
                  <search string="Search Leads">
                      <field name="name" string="Lead / Customer" filter_domain="['|','|','|',('partner_name','ilike',self),('email_from','ilike',self),('contact_name','ilike',self),('name','ilike',self)]"/>
 -                    <field name="categ_ids" string="Tag" filter_domain="[('categ_ids', 'ilike', self)]"/>
 -                    <field name="section_id" groups="base.group_multi_salesteams"/>
 +                    <field name="tag_ids" string="Tag" filter_domain="[('tag_ids', 'ilike', self)]"/>
 +                    <field name="team_id" groups="base.group_multi_salesteams"/>
                      <field name="user_id"/>
                      <field name="partner_id" operator="child_of"/>
                      <field name="create_date"/>
                              help="Leads that did not ask not to be included in mass mailing campaigns"/>
                      <group expand="0" string="Group By">
                          <filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
 -                        <filter string="Sales Team" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
 +                        <filter string="Sales Team" domain="[]" context="{'group_by':'team_id'}" groups="base.group_multi_salesteams"/>
                          <filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
                          <filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
                          <filter string="Campaign"  domain="[]" context="{'group_by':'campaign_id'}"/>
                          <button name="case_mark_lost" string="Mark Lost" type="object" class="oe_highlight"/>
                          <field name="stage_id" widget="statusbar" clickable="True"
                              options="{'fold_field': 'fold'}"
 -                            domain="['&amp;', ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"/>
 +                            domain="['&amp;', ('team_ids', '=', team_id), '|', ('type', '=', type), ('type', '=', 'both')]"/>
                      </header>
                      <sheet>
 -                        <div class="oe_right oe_button_box">
 +                        <div name="buttons" class="oe_right oe_button_box">
                              <button class="oe_inline oe_stat_button" type="action"
 -                                name="%(crm.crm_case_categ_phone_incoming0)d" icon="fa-phone"
 +                                name="%(crm.crm_phonecall_category_phone_incoming0)d" icon="fa-phone"
                                  context="{'default_opportunity_id': active_id, 'search_default_opportunity_id': active_id, 'default_partner_id': partner_id, 'default_duration': 1.0}">
 -                                <div>Schedule/Log<br/>Calls</div>
 +                                <div>Calls</div>
                              </button>
                              <button class="oe_inline oe_stat_button" type="object"
                                  context="{'partner_id': partner_id}"
                          </div>
                          <div class="oe_title">
                              <label for="name" class="oe_edit_only"/>
 -                            <h1><field name="name"/></h1>
 -                            <label for="planned_revenue" class="oe_edit_only"/>
 +                            <h1><field name="name" placeholder="e.g. Product Pricing"/></h1>
 +                            <label for="planned_revenue" style="padding-right: 50px" class="oe_edit_only"/>
 +                            <label for="probability" class="oe_edit_only"/>
                              <h2>
                                  <field name="company_currency" invisible="1"/>
                                  <field name="planned_revenue" class="oe_inline" widget='monetary' options="{'currency_field': 'company_currency'}"/>
  
                              <group>
                                  <field name="user_id" on_change="on_change_user(user_id, context)" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_sale_salesman_all_leads']}"/>
 -                                <label for="section_id" groups="base.group_multi_salesteams"/>
 +                                <label for="team_id" groups="base.group_multi_salesteams"/>
                                  <div groups="base.group_multi_salesteams">
 -                                    <field name="section_id" widget="selection"/>
 +                                    <field name="team_id" widget="selection"/>
                                      <button name="case_escalate" string="Escalate" type="object" class="oe_link"
 -                                            attrs="{'invisible': ['|', ('section_id','=',False), ('probability', '=', 100)]}"/>
 +                                            attrs="{'invisible': ['|', ('team_id','=',False), ('probability', '=', 100)]}"/>
                                  </div>
                              </group>
                              <group>
 -                                <field name="categ_ids"
 -                                    widget="many2many_tags"
 -                                    context="{'object_name': 'crm.lead'}"
 -                                    domain="[('object_id.model', '=', 'crm.lead')]"/>
 -
 +                                <field name="tag_ids" widget="many2many_tags"/>
 +                                <field name="lost_reason"/>
                              </group>
                          </group>
  
                          </page>
                          <page string="Fund Raising" groups="crm.group_fund_raising">
                              <group>
 -                                <field name="payment_mode" widget="selection"/>
                                  <field name="planned_cost"/>
                              </group>
                          </page>
                      <field name="stage_id"/>
                      <field name="planned_revenue" sum="Expected Revenues"/>
                      <field name="probability" avg="Avg. of Probability"/>
 -                    <field name="section_id" groups="base.group_multi_salesteams"/>
 +                    <field name="team_id" groups="base.group_multi_salesteams"/>
                      <field name="user_id"/>
                      <field name="referred" invisible="1"/>
                      <field name="priority" invisible="1"/>
              <field name="arch" type="xml">
                  <search string="Search Opportunities">
                      <field name="name" string="Opportunity" filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
 -                    <field name="categ_ids" string="Tag" filter_domain="[('categ_ids', 'ilike', self)]"/>
 -                    <field name="section_id" groups="base.group_multi_salesteams"/>
 +                    <field name="tag_ids" string="Tag" filter_domain="[('tag_ids', 'ilike', self)]"/>
 +                    <field name="team_id" groups="base.group_multi_salesteams"/>
                      <field name="user_id"/>
                      <field name="partner_id" operator="child_of"/>
                      <field name="stage_id" domain="[]"/>
                      <field name="probability"/>
 +                    <field name="lost_reason"/>
                      <separator/>
                      <filter string="New" name="new"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '&lt;=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.sequence', '&lt;=', 1)]"/>
                      <filter string="Won" name="won"
-                             domain="[('probability', '=', 100), ('stage_id.fold', '=', True)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 100), ('stage_id.fold', '=', True)]"/>
                      <filter string="Lost" name="lost"
-                             domain="[('probability', '=', 0), ('stage_id.fold', '=', True)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.fold', '=', True)]"/>
                      <separator/>
                      <filter string="My Opportunities" name="assigned_to_me"
                              domain="[('user_id', '=', uid)]"
                      <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                      <group expand="0" string="Group By" colspan="16">
                          <filter string="Salesperson" context="{'group_by':'user_id'}"/>
 -                        <filter string="Sales Team" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
 +                        <filter string="Sales Team" context="{'group_by':'team_id'}" groups="base.group_multi_salesteams"/>
                          <filter string="Country" context="{'group_by':'country_id'}" />
                          <filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
                          <filter string="Stage" context="{'group_by':'stage_id'}"/>
                          <separator orientation="vertical" />
                          <filter string="Creation Month" context="{'group_by':'create_date:month'}" name="month"/>
                          <filter string="Expected Closing" context="{'group_by':'date_deadline'}"/>
 +                        <filter string="Lost Reason" context="{'group_by':'lost_reason'}"/>
                      </group>
                  </search>
              </field>
@@@ -2,13 -2,13 +2,13 @@@
  <openerp>
      <data>
  
 -        <!-- Leads by user and section Graph View -->
 +        <!-- Leads by user and team Graph View -->
          <record id="view_report_crm_lead_graph" model="ir.ui.view">
              <field name="name">crm.lead.report.graph</field>
              <field name="model">crm.lead.report</field>
              <field name="arch" type="xml">
                  <graph string="Leads Analysis" type="pivot" stacked="True">
 -                    <field name="section_id" type="row"/>
 +                    <field name="team_id" type="row"/>
                      <field name="create_date" interval="month" type="col"/>
                      <field name="nbr_cases" type="measure"/>
                      <field name="probable_revenue" type="measure"/>
@@@ -32,7 -32,7 +32,7 @@@
              <field name="name">Long Term Revenue</field>
              <field name="model_id">crm.lead.report</field>
              <field name="user_id" eval="False"/>
 -            <field name="context">{'group_by': ['country_id', 'section_id'], 'col_group_by': ['date_deadline'], 'measures': ['planned_revenue']}</field>
 +            <field name="context">{'group_by': ['country_id', 'team_id'], 'col_group_by': ['date_deadline'], 'measures': ['planned_revenue']}</field>
          </record>
          <record id="filter_leads_revenue_per_lead" model="ir.filters">
              <field name="name">Revenue Per Lead</field>
@@@ -44,7 -44,7 +44,7 @@@
              <field name="name">Overpassed Deadline</field>
              <field name="model_id">crm.lead.report</field>
              <field name="user_id" eval="False"/>
 -            <field name="context">{'group_by': ['section_id'], 'col_group_by': ['partner_id'], 'measures': ['delay_expected']}</field>
 +            <field name="context">{'group_by': ['team_id'], 'col_group_by': ['partner_id'], 'measures': ['delay_expected']}</field>
          </record>
          <record id="filter_leads_salesperson" model="ir.filters">
              <field name="name">By Salespersons</field>
@@@ -59,7 -59,7 +59,7 @@@
              <field name="context">{'group_by': ['create_date:month', 'country_id']}</field>
          </record>
  
 -        <!-- Leads by user and section Search View -->
 +        <!-- Leads by user and team Search View -->
          <record id="view_report_crm_lead_filter" model="ir.ui.view">
              <field name="name">crm.lead.report.select</field>
              <field name="model">crm.lead.report</field>
                      <filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
                      <filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
                      <separator/>
-                     <filter string="New" name="new"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
                      <filter string="Won" name="won"
-                             domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
                      <filter string="Lost" name="lost"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
 -                    <field name="section_id" context="{'invisible_section': False}"
 +                    <field name="team_id" context="{'invisible_team': False}"
                              groups="base.group_multi_salesteams"/>
                      <field name="user_id" string="Salesperson"/>
                      <group expand="0" string="Extended Filters">
                          <field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
 -                        <field name="stage_id" domain="[('section_ids', '=', 'section_id')]" />
 +                        <field name="stage_id" domain="[('team_ids', '=', 'team_id')]" />
                          <field name="campaign_id"/>
                          <field name="medium_id"/>
                          <field name="source_id"/>
                          <field name="company_id" groups="base.group_multi_company"/>
                          <separator orientation="vertical"/>
 -                        <field name="stage_id" widget="selection" domain="[('section_ids', '=', 'section_id')]" />
 +                        <field name="stage_id" widget="selection" domain="[('team_ids', '=', 'team_id')]" />
                          <field name="campaign_id" widget="selection"/>
                          <field name="medium_id" widget="selection"/>
                          <field name="source_id" widget="selection"/>
@@@ -98,7 -96,7 +96,7 @@@
                      </group>
                      <group expand="1" string="Group By">
                          <filter string="Salesperson" context="{'group_by':'user_id'}" />
 -                        <filter string="Sales Team" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
 +                        <filter string="Sales Team" context="{'group_by':'team_id'}" groups="base.group_multi_salesteams"/>
                          <filter string="Country" context="{'group_by':'country_id'}" />
                          <filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
                          <filter string="Stage" context="{'group_by':'stage_id'}"/>
              </field>
          </record>
  
 -        <!-- Leads by user and section Action -->
 +        <!-- Leads by user and team Action -->
         <record id="action_report_crm_lead" model="ir.actions.act_window">
             <field name="name">Leads Analysis</field>
             <field name="res_model">crm.lead.report</field>
@@@ -2,7 -2,7 +2,7 @@@
  <openerp>
      <data>
  
 -        <!-- Opportunities by user and section Graph View -->
 +        <!-- Opportunities by user and team Graph View -->
          <record id="view_report_crm_opportunity_graph" model="ir.ui.view">
              <field name="name">crm.opportunity.report.graph</field>
              <field name="model">crm.opportunity.report</field>
@@@ -29,7 -29,7 +29,7 @@@
              <field name="model_id">crm.opportunity.report</field>
              <field name="domain">[('type','=','opportunity'), ('probability', '=', 100), ('stage_id.on_change', '=', 1)]</field>
              <field name="user_id" eval="False"/>
 -            <field name="context">{'group_by': ['section_id'], 'col_group_by': ['date_last_stage_update:month'], 'measures': ['nbr_cases', 'delay_close']}</field>
 +            <field name="context">{'group_by': ['team_id'], 'col_group_by': ['date_last_stage_update:month'], 'measures': ['nbr_cases', 'delay_close']}</field>
          </record>
          <record id="filter_opportunity_top_opportunities" model="ir.filters">
              <field name="name">Top Opportunities</field>
@@@ -51,7 -51,7 +51,7 @@@
              <field name="context">{'group_by': ['create_date:month', 'country_id']}</field>
          </record>
  
 -        <!-- Opportunities by user and section Search View -->
 +        <!-- Opportunities by user and team Search View -->
          <record id="view_report_crm_opportunity_filter" model="ir.ui.view">
              <field name="name">crm.opportunity.report.select</field>
              <field name="model">crm.opportunity.report</field>
                      <filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
                      <filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
                      <separator/>
-                     <filter string="New" name="new"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '&lt;=', 1)]"/>
                      <filter string="Won" name="won"
-                             domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
                      <filter string="Lost" name="lost"
-                             domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
+                             domain="['&amp;', ('stage_id.probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
 -                    <field name="section_id" context="{'invisible_section': False}"
 +                    <field name="team_id" context="{'invisible_team': False}"
                              groups="base.group_multi_salesteams"/>
                      <field name="user_id" string="Salesperson"/>
                      <group expand="0" string="Extended Filters">
                          <field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
 -                        <field name="stage_id" domain="[('section_ids', '=', 'section_id')]" />
 +                        <field name="stage_id" domain="[('team_ids', '=', 'team_id')]" />
                          <field name="campaign_id"/>
                          <field name="medium_id"/>
                          <field name="source_id"/>
                          <field name="company_id" groups="base.group_multi_company"/>
                          <separator orientation="vertical"/>
 -                        <field name="stage_id" widget="selection" domain="[('section_ids', '=', 'section_id')]" />
 +                        <field name="stage_id" widget="selection" domain="[('team_ids', '=', 'team_id')]" />
                          <field name="campaign_id" widget="selection"/>
                          <field name="medium_id" widget="selection"/>
                          <field name="source_id" widget="selection"/>
                      </group>
                      <group expand="1" string="Group By">
                          <filter string="Salesperson" context="{'group_by':'user_id'}" />
 -                        <filter string="Sales Team" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
 +                        <filter string="Sales Team" context="{'group_by':'team_id'}" groups="base.group_multi_salesteams"/>
                          <filter string="Country" context="{'group_by':'country_id'}" />
                          <filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
                          <filter string="Stage" context="{'group_by':'stage_id'}"/>
                          <separator orientation="vertical" />
                          <filter string="Creation Month" context="{'group_by':'create_date:month'}" name="month"/>
                          <filter string="Expiration Closing" context="{'group_by':'date_deadline:month'}" name="month" help="Expiration Closing Month"/>
 +                        <filter string="Lost Reason" context="{'group_by':'lost_reason'}"/>
                      </group>
                  </search>
              </field>
@@@ -5,11 -5,10 +5,11 @@@ import loggin
  import time
  import uuid
  import random
 -
 +import re
  import simplejson
 -
  import openerp
 +import cgi
 +
  from openerp.http import request
  from openerp.osv import osv, fields
  from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
@@@ -134,34 -133,6 +134,34 @@@ class im_chat_session(osv.Model)
                  user = self.pool['res.users'].read(cr, uid, user_id, ['name'], context=context)
                  self.pool["im_chat.message"].post(cr, uid, uid, session.uuid, "meta", user['name'] + " joined the conversation.", context=context)
  
 +    def remove_user(self, cr, uid, session_id, context=None):
 +        """ private implementation of removing a user from a given session (and notify the other people) """
 +        session = self.browse(cr, openerp.SUPERUSER_ID, session_id, context=context)
 +        # send a message to the conversation
 +        user = self.pool['res.users'].read(cr, uid, uid, ['name'], context=context)
 +        self.pool["im_chat.message"].post(cr, uid, uid, session.uuid, "meta", user['name'] + " left the conversation.", context=context)
 +        # close his session state, and remove the user from session
 +        self.update_state(cr, uid, session.uuid, 'closed', context=None)
 +        self.write(cr, uid, [session.id], {"user_ids": [(3, uid)]}, context=context)
 +        # notify the all the channel users and anonymous channel
 +        notifications = []
 +        for channel_user_id in session.user_ids:
 +            info = self.session_info(cr, channel_user_id.id, [session.id], context=context)
 +            notifications.append([(cr.dbname, 'im_chat.session', channel_user_id.id), info])
 +        # anonymous are not notified when a new user left : cannot exec session_info as uid = None
 +        info = self.session_info(cr, openerp.SUPERUSER_ID, [session.id], context=context)
 +        notifications.append([session.uuid, info])
 +        self.pool['bus.bus'].sendmany(cr, uid, notifications)
 +
 +    def quit_user(self, cr, uid, uuid, context=None):
 +        """ action of leaving a given session """
 +        sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1)
 +        for session in self.browse(cr, openerp.SUPERUSER_ID, sids, context=context):
 +            if uid and uid in [u.id for u in session.user_ids] and len(session.user_ids) > 2:
 +                self.remove_user(cr, uid, session.id, context=context)
 +                return True
 +            return False
 +
      def get_image(self, cr, uid, uuid, user_id, context=None):
          """ get the avatar of a user in the given session """
          #default image
@@@ -194,20 -165,6 +194,20 @@@ class im_chat_message(osv.Model)
          'type' : 'message',
      }
  
 +    def _escape_keep_url(self, message):
 +        """ escape the message and transform the url into clickable link """
 +        safe_message = ""
 +        first = 0
 +        last = 0
 +        for m in re.finditer('(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?', message):
 +            last = m.start()
 +            safe_message += cgi.escape(message[first:last])
 +            safe_message += '<a href="%s" target="_blank">%s</a>' % (cgi.escape(m.group(0)), m.group(0))
 +            first = m.end()
 +            last = m.end()
 +        safe_message += cgi.escape(message[last:])
 +        return safe_message
 +
      def init_messages(self, cr, uid, context=None):
          """ get unread messages and old messages received less than AWAY_TIMER
              ago and the session_info for open or folded window
          session_ids = Session.search(cr, uid, [('uuid','=',uuid)], context=context)
          notifications = []
          for session in Session.browse(cr, uid, session_ids, context=context):
 -            # build the new message
 +            # build and escape the new message
 +            message_content = self._escape_keep_url(message_content)
 +            message_content = self.pool['im_chat.shortcode'].replace_shortcode(cr, uid, message_content, context=context)
              vals = {
                  "from_id": from_uid,
                  "to_id": session.id,
          return False
  
  
 +class im_chat_shortcode(osv.Model):
 +    """ Message shortcuts """
 +    _name = "im_chat.shortcode"
 +
 +    _columns = {
 +        'source' : fields.char('Shortcut', required=True, select=True, help="The shortcut which must be replace in the Chat Messages"),
 +        'substitution' : fields.char('Substitution', required=True, select=True, help="The html code replacing the shortcut"),
 +        'description' : fields.char('Description'),
 +    }
 +
 +    def replace_shortcode(self, cr, uid, message, context=None):
 +        ids = self.search(cr, uid, [], context=context)
 +        for shortcode in self.browse(cr, uid, ids, context=context):
 +            regex = "(?:^|\s)(%s)(?:\s|$)" % re.escape(shortcode.source)
 +            message = re.sub(regex, " " + shortcode.substitution + " ", message)
 +        return message
 +
 +
  class im_chat_presence(osv.Model):
      """ im_chat_presence status can be: online, away or offline.
          This model is a one2one, but is not attached to res_users to avoid database concurrence errors
      _name = 'im_chat.presence'
  
      _columns = {
-         'user_id' : fields.many2one('res.users', 'Users', required=True, select=True),
+         'user_id' : fields.many2one('res.users', 'Users', required=True, select=True, ondelete="cascade"),
          'last_poll': fields.datetime('Last Poll'),
          'last_presence': fields.datetime('Last Presence'),
          'status' : fields.selection([('online','Online'), ('away','Away'), ('offline','Offline')], 'IM Status'),
@@@ -44,8 -44,6 +44,8 @@@ class MassMailingContact(osv.Model)
              ondelete='cascade', required=True,
          ),
          'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this list'),
 +        'unsubscription_date': fields.datetime('Unsubscription Date'),
 +        'message_bounce': fields.integer('Bounce', help='Counter of the number of bounced emails for this contact.'),
      }
  
      def _get_latest_list(self, cr, uid, context={}):
          'list_id': _get_latest_list
      }
  
 +    def on_change_opt_out(self, cr, uid, id, opt_out, context=None):
 +        return {'value': {
 +            'unsubscription_date': opt_out and fields.datetime.now() or False,
 +        }}
 +
 +    def create(self, cr, uid, vals, context=None):
 +        if 'opt_out' in vals:
 +            vals['unsubscription_date'] = vals['opt_out'] and fields.datetime.now() or False
 +        return super(MassMailingContact, self).create(cr, uid, vals, context=context)
 +
 +    def write(self, cr, uid, ids, vals, context=None):
 +        if 'opt_out' in vals:
 +            vals['unsubscription_date'] = vals['opt_out'] and fields.datetime.now() or False
 +        return super(MassMailingContact, self).write(cr, uid, ids, vals, context=context)
 +
      def get_name_email(self, name, context):
          name, email = self.pool['res.partner']._parse_partner_name(name, context=context)
          if name and not email:
              res[record.id] = {'partner_ids': [], 'email_to': record.email, 'email_cc': False}
          return res
  
 +    def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
 +        """Called by ``message_process`` when a bounce email (such as Undelivered
 +        Mail Returned to Sender) is received for an existing thread. As contacts
 +        do not inherit form mail.thread, we have to define this method to be able
 +        to track bounces (see mail.thread for more details). """
 +        for obj in self.browse(cr, uid, ids, context=context):
 +            self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
 +
  
  class MassMailingList(osv.Model):
      """Model of a contact list. """
  
      _columns = {
          'name': fields.char('Mailing List', required=True),
 +        'create_date': fields.datetime('Creation Date'),
          'contact_nbr': fields.function(
              _get_contact_nbr, type='integer',
              string='Number of Contacts',
@@@ -383,7 -357,6 +383,7 @@@ class MassMailing(osv.Model)
              'ir.attachment', 'mass_mailing_ir_attachments_rel',
              'mass_mailing_id', 'attachment_id', 'Attachments'
          ),
 +        'keep_archives': fields.boolean('Keep Archives'),
          'mass_mailing_campaign_id': fields.many2one(
              'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
              ondelete='set null',
              comp_ctx = dict(context, active_ids=res_ids)
              composer_values = {
                  'author_id': author_id,
+                 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
                  'body': mailing.body_html,
                  'subject': mailing.name,
                  'model': mailing.mailing_model,
diff --combined addons/mrp/mrp_view.xml
              <field name="model">mrp.property.group</field>
              <field name="arch" type="xml">
                  <form string="Properties categories">
 -                    <label for="name"/>
 -                    <field name="name"/>
 -                    <label for="description"/>
 -                    <field name="description"/>
 +                    <group>
 +                        <field name="name"/>
 +                    </group>
 +                    <separator string="Description"/>
 +                    <field name="description" nolabel="1"/>
                  </form>
              </field>
          </record>
              parent="base.menu_mrp_root"
              groups="group_mrp_manager"
              sequence="50"/>
 +        <menuitem
 +            id="menu_mrp_configuration_manufacturing"
 +            name="Manufacturing"
 +            parent="menu_mrp_configuration"
 +            groups="group_mrp_manager"
 +            sequence="1"/>
 +        <menuitem
 +            id="menu_mrp_configuration_products"
 +            name="Products"
 +            parent="menu_mrp_configuration"
 +            groups="group_mrp_manager"
 +            sequence="2"/>
 +
 +
  
          <record id="mrp_property_tree_view" model="ir.ui.view">
              <field name="name">mrp.property.tree</field>
@@@ -99,8 -84,8 +99,8 @@@
                          <field name="group_id"/>
                          <field name="composition"/>
                      </group>
 -                    <label for="description"/>
 -                    <field name="description"/>
 +                    <separator string="Description"/>
 +                    <field name="description" nolabel="1"/>
                  </form>
              </field>
          </record>
              action="mrp_property_action"
              id="menu_mrp_property_action"
              groups="product.group_mrp_properties"
 -            parent="menu_mrp_configuration"
 +            parent="menu_mrp_configuration_products"
              sequence="30"/>
          <menuitem
              action="mrp_property_group_action"
 -            parent="menu_mrp_configuration"
 +            parent="menu_mrp_configuration_products"
              groups="base.group_no_one,product.group_mrp_properties"
              id="menu_mrp_property_group_action"
              sequence="35"/>
                                      <field name="costs_general_account_id" attrs="{ 'required':['|',('costs_cycle_account_id', '=', True),('costs_hour_account_id', '=', True)]}" groups="analytic.group_analytic_accounting"/>
                                  </group>
                              </group>
 -                            <label for="note"/>
 -                            <field name="note"/>
 +                            <separator string="Description"/>
 +                            <field name="note" nolabel="1"/>
                          </page>
                      </notebook>
                  </form>
                              </div>
                          </group>
                          <group>
 -                            <field name="name" groups="product.group_mrp_properties"/>
 +                            <field name="name" groups="mrp.group_route_line_type"/>
                              <field name="code" string="Reference"/>
 -                            <field name="type"/>
 +                            <field name="type" widget="radio"/>
                              <p colspan="2" class="oe_grey" attrs="{'invisible': [('type','=','normal')]}">
                                  When processing a sales order for this product, the delivery order
                                  will contain the raw materials, instead of the finished product.
                                  <tree string="Components" editable="bottom">
                                      <field name="sequence" widget="handle"/>
                                      <field name="product_id" on_change="onchange_product_id(product_id, product_qty)"/>
 -                                    <field name="type"/>
 +                                    <field name="type" groups="mrp.group_route_line_type"/>
                                      <field name="product_qty"/>
                                      <field name="product_uom" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
 -                                    <field name="product_rounding"/>
 -                                    <field name="product_efficiency"/>
 +                                    <field name="product_rounding" groups="mrp.group_rounding_efficiency"/>
 +                                    <field name="product_efficiency" groups="mrp.group_rounding_efficiency"/>
                                      <field name="date_start"/>
                                      <field name="date_stop"/>
                                      <field name="attribute_value_ids" widget="many2many_tags" domain="[('product_ids.product_tmpl_id', '=', parent.product_tmpl_id)]"/>
              </field>
          </record>
  
 -       <record id="mrp_bom_component_tree_view" model="ir.ui.view">
 +        <record id="mrp_bom_component_tree_view" model="ir.ui.view">
              <field name="name">mrp.bom.component.tree</field>
              <field name="model">mrp.bom.line</field>
              <field name="arch" type="xml">
              </field>
          </record>
  
 -        <record id="mrp_bom_form_action2" model="ir.actions.act_window">
 -            <field name="name">Bill of Material Components</field>
 -            <field name="type">ir.actions.act_window</field>
 -            <field name="res_model">mrp.bom.line</field>
 -            <field name="view_type">tree</field>
 -            <field name="view_mode">tree</field>
 -            <field name="view_id" eval="False"/> <!-- Force empty -->
 -            <field name="help" type="html">
 -              <p class="oe_view_nocontent_create">
 -                Click to add a component to a bill of material.
 -              </p><p>
 -                Bills of materials components are components and by-products
 -                used to create master bills of materials.  Use this menu to
 -                search in which BoM a specific component is used.
 -              </p>
 -            </field>
 -        </record>
 -
          <record id="product_template_search_view_procurment" model="ir.ui.view">
               <field name="name">product.template.search.bom</field>
               <field name="model">product.template</field>
              action="mrp_bom_form_action"
              id="menu_mrp_bom_form_action"
              parent="menu_mrp_bom"
 -            sequence="10"/>
 +            sequence="99"/>
         <menuitem name="Products" 
              id="menu_mrp_product_form" 
              action="product_template_action"
              parent="menu_mrp_bom"/>
 -        <menuitem
 -            action="mrp_bom_form_action2"
 -            id="menu_mrp_bom_form_action2"
 -            parent="menu_mrp_configuration"
 -            sequence="20"/>
  
          <record id="action2" model="ir.actions.act_window">
              <field name="name">Bill of Materials Structure</field>
                              <field name="date_planned"/>
                          </group>
                          <group>
-                             <field name="bom_id" domain="[('product_id','=',product_id)]" context="{'default_product_id': product_id}" on_change="bom_id_change(bom_id)" required="1"/>
+                             <field name="bom_id"
+                                 domain="['|',
+                                             ('product_id','=',product_id),
+                                             '&amp;',
+                                                 ('product_tmpl_id.product_variant_ids','=',product_id),
+                                                 ('product_id','=',False)]"
+                                 context="{'default_product_id': product_id}" on_change="bom_id_change(bom_id)" required="1"/>
                              <field name="routing_id" class="oe_inline" groups="mrp.group_mrp_routings"/>
                              <field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'mrp.group_mrp_user']}"/>
                              <field name="origin"/>
              </field>
          </record>
  
 -        <menuitem id="menu_pm_resources_config" name="Resources" parent="menu_mrp_configuration"/>
 -        <menuitem action="mrp_workcenter_action" id="menu_view_resource_search_mrp" groups="mrp.group_mrp_routings" parent="mrp.menu_mrp_configuration" sequence="25"/>
 +        <menuitem id="menu_pm_resources_config" name="Resources" parent="menu_mrp_configuration" sequence="3"/>
 +        <menuitem action="mrp_workcenter_action" id="menu_view_resource_search_mrp" groups="mrp.group_mrp_routings" parent="mrp.menu_mrp_configuration_manufacturing" sequence="25"/>
          <menuitem action="resource.action_resource_calendar_form" id="menu_view_resource_calendar_search_mrp" parent="menu_pm_resources_config" sequence="1" groups="base.group_no_one"/>
          <menuitem action="resource.action_resource_calendar_leave_tree" id="menu_view_resource_calendar_leaves_search_mrp" parent="menu_pm_resources_config" sequence="1" groups="base.group_no_one"/>
  
@@@ -20,7 -20,7 +20,7 @@@ access_stock_location_mrp_worker,stock.
  access_stock_move_mrp_worker,stock.move mrp_worker,stock.model_stock_move,mrp.group_mrp_user,1,1,1,0
  access_stock_picking_mrp_worker,stock.picking mrp_worker,stock.model_stock_picking,mrp.group_mrp_user,1,1,1,1
  access_stock_warehouse,stock.warehouse mrp_worker,stock.model_stock_warehouse,mrp.group_mrp_user,1,0,0,0
 -access_account_analytic_journal_mrp_worker,account.analytic.journal mrp_worker,account.model_account_analytic_journal,mrp.group_mrp_user,1,0,0,0
 +access_account_analytic_journal_mrp_worker,account.analytic.journal mrp_worker,analytic.model_account_analytic_journal,mrp.group_mrp_user,1,0,0,0
  access_account_account,account.account mrp_worker,account.model_account_account,mrp.group_mrp_user,1,0,0,0
  access_hr_timesheet_group_mrp_worker,resource.calendar mrp_manager,resource.model_resource_calendar,mrp.group_mrp_manager,1,1,1,1
  access_procurement_user,procurement.order.user,model_procurement_order,base.group_user,1,1,1,1
@@@ -28,6 -28,7 +28,6 @@@ access_mrp_production_stock_worker,mrp.
  access_report_workcenter_load,report.workcenter.load,model_report_workcenter_load,mrp.group_mrp_manager,1,1,1,1
  access_report_mrp_inout,report.mrp.inout,model_report_mrp_inout,mrp.group_mrp_manager,1,1,1,1
  access_ir_property_manager,ir.property manager,base.model_ir_property,mrp.group_mrp_manager,1,1,1,1
 -access_account_sequence_fiscalyear,account.sequence.fiscalyear,account.model_account_sequence_fiscalyear,mrp.group_mrp_user,1,1,1,1
  access_product_product_user,product.product user,product.model_product_product,mrp.group_mrp_user,1,0,0,0
  access_product_template_user,product.template user,product.model_product_template,mrp.group_mrp_user,1,0,0,0
  access_product_uom_user,product.uom user,product.model_product_uom,mrp.group_mrp_user,1,0,0,0
@@@ -41,12 -42,14 +41,12 @@@ access_resource_calendar_attendance_man
  access_product_puom_categ,product.uom.categ,product.model_product_uom_categ,mrp.group_mrp_user,1,0,0,0
  access_resource_resource,resource.resource,resource.model_resource_resource,mrp.group_mrp_user,1,0,0,0
  access_board_board_user,mrp.board.board,board.model_board_board,mrp.group_mrp_user,1,0,0,0
 -access_account_sequence_fiscalyear_manager,account.sequence.fiscalyear,account.model_account_sequence_fiscalyear,mrp.group_mrp_manager,1,0,0,0
  access_product_supplierinfo_manager,product.supplierinfo user,product.model_product_supplierinfo,mrp.group_mrp_manager,1,0,0,0
  access_mrp_production_manager,mrp.production manager,model_mrp_production,mrp.group_mrp_manager,1,0,0,0
  access_procurement_manager,procurement.order,model_procurement_order,mrp.group_mrp_manager,1,0,0,0
  access_workcenter_manager,mrp.production.workcenter.line,model_mrp_production_workcenter_line,mrp.group_mrp_manager,1,0,0,0
  access_stock_move_mrp_manager,stock.move mrp_manager,stock.model_stock_move,mrp.group_mrp_manager,1,0,0,0
  access_mrp_production_product_line_manager,mrp.production.product.line manager,model_mrp_production_product_line,mrp.group_mrp_manager,1,0,0,0
 -access_account_sequence_fiscalyear_system,account.sequence.fiscalyear.system,account.model_account_sequence_fiscalyear,mrp.group_mrp_manager,1,0,0,0
  access_stock_production_lot_user,stock.production.lot,stock.model_stock_production_lot,mrp.group_mrp_user,1,1,1,1
  access_stock_warehouse_orderpoint_user,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,mrp.group_mrp_user,1,0,0,0
  access_stock_picking_mrp_manager,stock.picking mrp_manager,stock.model_stock_picking,mrp.group_mrp_manager,1,0,0,0
@@@ -73,9 -76,7 +73,9 @@@ access_product_pricelist_version_mrp_ma
  access_product_pricelist_item_mrp_manager,product.pricelist.item mrp_manager,product.model_product_pricelist_item,mrp.group_mrp_manager,1,1,1,1
  access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,resource.model_resource_calendar,mrp.group_mrp_user,1,0,0,0
  access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
- access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
- access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
+ access_mrp_property_group_manager,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
+ access_mrp_property_manager,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
  access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
  access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0
 +access_mrp_bom_invoicing_payment,mrp.bom,model_mrp_bom,account.group_account_invoice,1,0,0,0
 +access_mrp_production_invoicing_payment,mrp.production,model_mrp_production,account.group_account_invoice,1,1,1,0
@@@ -75,11 -75,6 +75,11 @@@ class PaymentAcquirer(osv.Model)
          'website_published': fields.boolean(
              'Visible in Portal / Website', copy=False,
              help="Make this payment acquirer available (Customer invoices, etc.)"),
 +        'auto_confirm': fields.selection(
 +            [('none', 'No automatic confirmation'),
 +             ('at_pay_confirm', 'At payment confirmation'),
 +             ('at_pay_now', 'At payment')],
 +            string='Order Confirmation', required=True),
          # Fees
          'fees_active': fields.boolean('Compute fees'),
          'fees_dom_fixed': fields.float('Fixed domestic fees'),
@@@ -93,7 -88,6 +93,7 @@@
          'environment': 'test',
          'validation': 'automatic',
          'website_published': True,
 +        'auto_confirm': 'at_pay_confirm',
      }
  
      def _check_required_if_provider(self, cr, uid, ids, context=None):
  
               - partner_values: will contain name, lang, email, zip, address, city,
                 country_id (int or False), country (browse or False), phone, reference
-              - tx_values: will contain refernece, amount, currency_id (int or False),
+              - tx_values: will contain reference, amount, currency_id (int or False),
                 currency (browse or False), partner (browse or False)
          """
          acquirer = self.browse(cr, uid, id, context=context)
@@@ -20,7 -20,7 +20,8 @@@
                                  <field name="company_id"/>
                                  <field name="website_published"/>
                                  <field name="environment"/>
+                                 <field name="validation"/>
 +                                <field name="auto_confirm"/>
                              </group>
                              <group>
                                  <field name="fees_active"/>
          <menuitem
              action="product_template_action"
              id="menu_pos_products"
 -            parent="menu_point_of_sale_product" sequence="2"/>
 +            parent="menu_point_of_sale_product" sequence="0"/>
  
          <record model="ir.ui.view" id="view_pos_order_line">
              <field name="name">Sale lines</field>
              <field name="field_parent" eval="False"/>
              <field name="arch" type="xml">
                  <tree string="Product Product Categories">
-                     <field name="sequence" invisible="1"/>
+                     <field name="sequence" widget="handle"/>
                      <field name="complete_name"/>
                  </tree>
              </field>
                </p>
              </field>
          </record>
 -        <menuitem action="product_pos_category_action" id="menu_product_pos_category" parent="menu_point_of_sale_product" sequence="0" />
 +        <menuitem action="product_pos_category_action" id="menu_product_pos_category" parent="menu_point_of_sale_product" sequence="2" />
          <!-- END -->
  
          <record id="action_edit_ean" model="ir.actions.act_window">
          </record>
  
          <!--  Miscellaneous Operations/Reporting -->
 -        <menuitem name="Point of Sale" parent="base.menu_reporting" id="menu_point_rep" sequence="50" groups="group_pos_manager"/>
 +        <menuitem name="Point of Sale" parent="base.menu_reporting" id="menu_point_rep" sequence="4" groups="group_pos_manager"/>
          <!-- Invoice -->
  
          <record model="ir.actions.act_window" id="action_pos_invoice">
                                  <field name="iface_invoicing" />
                              </group>
                              <group>
 +                                <field name="iface_fullscreen" />
                                  <field name="iface_big_scrollbars" />
                              </group>
                          </group>
  
      </data>
  </openerp>
 +
@@@ -169,7 -169,7 +169,7 @@@ function openerp_pos_models(instance, m
          },{
              model:  'res.partner',
              fields: ['name','street','city','state_id','country_id','vat','phone','zip','mobile','email','ean13','write_date'],
-             domain: null,
+             domain: [['customer','=',true]],
              loaded: function(self,partners){
                  self.partners = partners;
                  self.db.add_partners(partners);
              fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 
                       'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
                       'product_tmpl_id'],
-             domain:  function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; },
+             domain: [['sale_ok','=',true],['available_in_pos','=',true]],
              context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; },
              loaded: function(self, products){
                  self.db.add_products(products);
              label: 'fonts',
              loaded: function(self){
                  var fonts_loaded = new $.Deferred();
 -
                  // Waiting for fonts to be loaded to prevent receipt printing
                  // from printing empty receipt while loading Inconsolata
                  // ( The font used for the receipt ) 
                  waitForWebfonts(['Lato','Inconsolata'], function(){
                      fonts_loaded.resolve();
                  });
 -
                  // The JS used to detect font loading is not 100% robust, so
                  // do not wait more than 5sec
                  setTimeout(function(){
                      } else {
                          def.reject();
                      }
-                 }, function(){ def.reject(); });    
+                 }, function(err,event){ event.preventDefault(); def.reject(); });    
              return def;
          },
  
          // wrapper around the _save_to_server that updates the synch status widget
          _flush_orders: function(orders, options) {
              var self = this;
 -
              this.set('synch',{ state: 'connecting', pending: orders.length});
  
              return self._save_to_server(orders, options).done(function (server_ids) {
                  return server_ids;
              }).fail(function (error, event){
                  if(error.code === 200 ){    // Business Logic Error, not a connection problem
 +                    //if warning do not need to dispaly traceback!!
 +                    if(error.data.exception_type == 'warning'){
 +                        delete error.data.debug;
 +                    }
                      self.pos_widget.screen_selector.show_popup('error-traceback',{
                          message: error.data.message,
                          comment: error.data.debug
              this.screen_data = {};  // see ScreenSelector
              this.receipt_type = 'receipt';  // 'receipt' || 'invoice'
              this.temporary = attributes.temporary || false;
 +            this.to_invoice = false;
              return this;
          },
          is_empty: function(){
                  return sum + paymentLine.get_amount();
              }), 0);
          },
 -        getChange: function() {
 -            return this.getPaidTotal() - this.getTotalTaxIncluded();
 +        getChange: function(paymentline) {
 +            if (!paymentline) {
 +                var change = this.getPaidTotal() - this.getTotalTaxIncluded();
 +            } else {
 +                var change = -this.getTotalTaxIncluded(); 
 +                var lines  = this.get('paymentLines').models;
 +                for (var i = 0; i < lines.length; i++) {
 +                    change += lines[i].get_amount();
 +                    if (lines[i] === paymentline) {
 +                        break;
 +                    }
 +                }
 +            }
 +            return round_pr(Math.max(0,change), this.pos.currency.rounding);
 +        },
 +        getDueLeft: function(paymentline) {
 +            if (!paymentline) {
 +                var due = this.getTotalTaxIncluded() - this.getPaidTotal();
 +            } else {
 +                var due = this.getTotalTaxIncluded();
 +                var lines = this.get('paymentLines').models;
 +                for (var i = 0; i < lines.length; i++) {
 +                    if (lines[i] === paymentline) {
 +                        break;
 +                    } else {
 +                        due -= lines[i].get_amount();
 +                    }
 +                }
 +            }
 +            return round_pr(Math.max(0,due), this.pos.currency.rounding);
 +        },
 +        isPaid: function(){
 +            return this.getDueLeft() === 0;
          },
 -        getDueLeft: function() {
 -            return this.getTotalTaxIncluded() - this.getPaidTotal();
 +        isPaidWithCash: function(){
 +            return !!this.get('paymentLines').find( function(pl){
 +                return pl.cashregister.journal.type === 'cash';
 +            });
 +        },
 +        finalize: function(){
 +            this.destroy();
          },
          // sets the type of receipt 'receipt'(default) or 'invoice'
          set_receipt_type: function(type){
                  }
              }
          },
 +        set_to_invoice: function(to_invoice) {
 +            this.to_invoice = to_invoice;
 +        },
 +        is_to_invoice: function(){
 +            return this.to_invoice;
 +        },
 +        // remove all the paymentlines with zero money in it
 +        clean_empty_paymentlines: function() {
 +            var lines = this.get('paymentLines').models;
 +            var empty = [];
 +            for ( var i = 0; i < lines.length; i++) {
 +                if (!lines[i].get_amount()) {
 +                    empty.push(lines[i]);
 +                }
 +            }
 +            for ( var i = 0; i < empty.length; i++) {
 +                this.removePaymentline(empty[i]);
 +            }
 +        },
          //see set_screen_data
          get_screen_data: function(key){
              return this.screen_data[key];
@@@ -211,6 -211,26 +211,6 @@@ function openerp_pos_screens(instance, 
          barcode_error_action: function(code){
              this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
          },
 -        // shows an action bar on the screen. The actionbar is automatically shown when you add a button
 -        // with add_action_button()
 -        show_action_bar: function(){
 -            this.pos_widget.action_bar.show();
 -        },
 -
 -        // hides the action bar. The actionbar is automatically hidden when it is empty
 -        hide_action_bar: function(){
 -            this.pos_widget.action_bar.hide();
 -        },
 -
 -        // adds a new button to the action bar. The button definition takes three parameters, all optional :
 -        // - label: the text below the button
 -        // - icon:  a small icon that will be shown
 -        // - click: a callback that will be executed when the button is clicked.
 -        // the method returns a reference to the button widget, and automatically show the actionbar.
 -        add_action_button: function(button_def){
 -            this.show_action_bar();
 -            return this.pos_widget.action_bar.add_new_button(button_def);
 -        },
  
          // this method shows the screen and sets up all the widget related to this screen. Extend this method
          // if you want to alter the behavior of the screen.
                  this.$el.removeClass('oe_hidden');
              }
  
 -            if(this.pos_widget.action_bar.get_button_count() > 0){
 -                this.show_action_bar();
 -            }else{
 -                this.hide_action_bar();
 -            }
 -            
              var self = this;
  
              this.pos_widget.set_numpad_visible(this.show_numpad);
              if(this.pos.barcode_reader){
                  this.pos.barcode_reader.reset_action_callbacks();
              }
 -            this.pos_widget.action_bar.destroy_buttons();
          },
  
          // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
          },
      });
  
 +    module.FullscreenPopup = module.PopUpWidget.extend({
 +        template:'FullscreenPopupWidget',
 +        show: function(){
 +            var self = this;
 +            this._super();
 +            this.renderElement();
 +            this.$('.button.fullscreen').off('click').click(function(){
 +                window.document.body.webkitRequestFullscreen();
 +                self.pos_widget.screen_selector.close_popup();
 +            });
 +            this.$('.button.cancel').off('click').click(function(){
 +                self.pos_widget.screen_selector.close_popup();
 +            });
 +        },
 +        ismobile: function(){
 +            return typeof window.orientation !== 'undefined'; 
 +        }
 +    });
 +
 +
      module.ErrorPopupWidget = module.PopUpWidget.extend({
          template:'ErrorPopupWidget',
          show: function(options){
          },
      });
  
 -    module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
 -        template: 'ErrorNoClientPopupWidget',
 -    });
 -
      module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
          template: 'ErrorInvoiceTransferPopupWidget',
      });
  
          init: function(parent, options){
              this._super(parent, options);
 +            this.partner_cache = new module.DomCache();
          },
  
          show_leftpane: false,
              contents.innerHTML = "";
              for(var i = 0, len = Math.min(partners.length,1000); i < len; i++){
                  var partner    = partners[i];
 -                var clientline_html = QWeb.render('ClientLine',{widget: this, partner:partners[i]});
 -                var clientline = document.createElement('tbody');
 -                clientline.innerHTML = clientline_html;
 -                clientline = clientline.childNodes[1];
 -
 +                var clientline = this.partner_cache.get_node(partner.id);
 +                if(!clientline){
 +                    var clientline_html = QWeb.render('ClientLine',{widget: this, partner:partners[i]});
 +                    var clientline = document.createElement('tbody');
 +                    clientline.innerHTML = clientline_html;
 +                    clientline = clientline.childNodes[1];
 +                    this.partner_cache.cache_node(partner.id,clientline);
 +                }
                  if( partners === this.new_client ){
                      clientline.classList.add('highlight');
                  }else{
                      clientline.classList.remove('highlight');
                  }
 -
                  contents.appendChild(clientline);
              }
          },
  
              new instance.web.Model('res.partner').call('create_from_ui',[fields]).then(function(partner_id){
                  self.saved_client_details(partner_id);
+             },function(err,event){
+                 event.preventDefault();
+                 self.pos_widget.screen_selector.show_popup('error',{
+                     'message':_t('Error: Could not Save Changes'),
+                     'comment':_t('Your Internet connection is probably down.'),
+                 });
              });
          },
          
  
      module.ReceiptScreenWidget = module.ScreenWidget.extend({
          template: 'ReceiptScreenWidget',
 -
          show_numpad:     false,
          show_leftpane:   false,
  
              this._super();
              var self = this;
  
 -            var print_button = this.add_action_button({
 -                    label: _t('Print'),
 -                    icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
 -                    click: function(){ self.print(); },
 -                });
 -
 -            var finish_button = this.add_action_button({
 -                    label: _t('Next Order'),
 -                    icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
 -                    click: function() { self.finishOrder(); },
 -                });
 -
              this.refresh();
  
              if (!this.pos.get('selectedOrder')._printed) {
                  this.print();
              }
  
 -            //
              // The problem is that in chrome the print() is asynchronous and doesn't
              // execute until all rpc are finished. So it conflicts with the rpc used
              // to send the orders to the backend, and the user is able to go to the next 
              // 2 seconds is the same as the default timeout for sending orders and so the dialog
              // should have appeared before the timeout... so yeah that's not ultra reliable. 
  
 -            finish_button.set_disabled(true);   
 +            this.lock_screen(true);  
              setTimeout(function(){
 -                finish_button.set_disabled(false);
 +                self.lock_screen(false);  
              }, 2000);
          },
 +        lock_screen: function(locked) {
 +            this._locked = locked;
 +            if (locked) {
 +                this.$('.next').removeClass('highlight');
 +            } else {
 +                this.$('.next').addClass('highlight');
 +            }
 +        },
          print: function() {
              this.pos.get('selectedOrder')._printed = true;
              window.print();
          },
 -        finishOrder: function() {
 -            this.pos.get('selectedOrder').destroy();
 +        finish_order: function() {
 +            if (!this._locked) {
 +                this.pos.get_order().finalize();
 +            }
 +        },
 +        renderElement: function() {
 +            var self = this;
 +            this._super();
 +            this.$('.next').click(function(){
 +                self.finish_order();
 +            });
 +            this.$('.button.print').click(function(){
 +                self.print();
 +            });
          },
          refresh: function() {
 -            var order = this.pos.get('selectedOrder');
 -            $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{
 +            var order = this.pos.get_order();
 +            this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
                      widget:this,
                      order: order,
                      orderlines: order.get('orderLines').models,
                      paymentlines: order.get('paymentLines').models,
                  }));
          },
 -        close: function(){
 -            this._super();
 -        }
      });
  
 -
      module.PaymentScreenWidget = module.ScreenWidget.extend({
 -        template: 'PaymentScreenWidget',
 -        back_screen: 'products',
 -        next_screen: 'receipt',
 +        template:      'PaymentScreenWidget',
 +        back_screen:   'product',
 +        next_screen:   'receipt',
 +        show_leftpane: false,
 +        show_numpad:   false,
          init: function(parent, options) {
              var self = this;
 -            this._super(parent,options);
 +            this._super(parent, options);
  
              this.pos.bind('change:selectedOrder',function(){
 -                    this.bind_events();
                      this.renderElement();
 +                    this.watch_order_changes();
                  },this);
 +            this.watch_order_changes();
  
 -            this.bind_events();
 -
 -            this.line_delete_handler = function(event){
 -                var node = this;
 -                while(node && !node.classList.contains('paymentline')){
 -                    node = node.parentNode;
 -                }
 -                if(node){
 -                    self.pos.get('selectedOrder').removePaymentline(node.line)   
 +            this.inputbuffer = "";
 +            this.firstinput  = true;
 +            this.keyboard_handler = function(event){
 +                var key = '';
 +                if ( event.keyCode === 13 ) {         // Enter
 +                    self.validate_order();
 +                } else if ( event.keyCode === 190 ) { // Dot
 +                    key = '.';
 +                } else if ( event.keyCode === 46 ) {  // Delete
 +                    key = 'CLEAR';
 +                } else if ( event.keyCode === 8 ) {   // Backspace 
 +                    key = 'BACKSPACE';
 +                    event.preventDefault(); // Prevents history back nav
 +                } else if ( event.keyCode >= 48 && event.keyCode <= 57 ){       // Numbers
 +                    key = '' + (event.keyCode - 48);
 +                } else if ( event.keyCode >= 96 && event.keyCode <= 105 ){      // Numpad Numbers
 +                    key = '' + (event.keyCode - 96);
 +                } else if ( event.keyCode === 189 || event.keyCode === 109 ) {  // Minus
 +                    key = '-';
 +                } else if ( event.keyCode === 107 ) { // Plus
 +                    key = '+';
                  }
 -                event.stopPropagation();
 -            };
  
 -            this.line_change_handler = function(event){
 -                var node = this;
 -                while(node && !node.classList.contains('paymentline')){
 -                    node = node.parentNode;
 -                }
 -                if(node){
 -                    node.line.set_amount(this.value);
 -                }
 -            };
 +                self.payment_input(key);
  
 -            this.line_click_handler = function(event){
 -                var node = this;
 -                while(node && !node.classList.contains('paymentline')){
 -                    node = node.parentNode;
 -                }
 -                if(node){
 -                    self.pos.get('selectedOrder').selectPaymentline(node.line);
 -                }
              };
 -
 -            this.hotkey_handler = function(event){
 -                if(event.which === 13){
 -                    self.validate_order();
 -                }else if(event.which === 27){
 -                    self.back();
 -                }
 -            };
 -
          },
 -        show: function(){
 -            this._super();
 -            var self = this;
 -            
 -            this.enable_numpad();
 -            this.focus_selected_line();
 -            
 -            document.body.addEventListener('keyup', this.hotkey_handler);
 -
 -            this.add_action_button({
 -                    label: _t('Back'),
 -                    icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
 -                    click: function(){  
 -                        self.back();
 -                    },
 -                });
 -
 -            this.add_action_button({
 -                    label: _t('Validate'),
 -                    name: 'validation',
 -                    icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
 -                    click: function(){
 -                        self.validate_order();
 -                    },
 -                });
 -           
 -            if( this.pos.config.iface_invoicing ){
 -                this.add_action_button({
 -                        label: 'Invoice',
 -                        name: 'invoice',
 -                        icon: '/point_of_sale/static/src/img/icons/png48/invoice.png',
 -                        click: function(){
 -                            self.validate_order({invoice: true});
 -                        },
 -                    });
 +        // resets the current input buffer
 +        reset_input: function(){
 +            var line = this.pos.get_order().selected_paymentline;
 +            this.firstinput  = true;
 +            if (line) {
 +                this.inputbuffer = this.format_currency_no_symbol(line.get_amount());
 +            } else {
 +                this.inputbuffer = "";
              }
 -
 -            if( this.pos.config.iface_cashdrawer ){
 -                this.add_action_button({
 -                        label: _t('Cash'),
 -                        name: 'cashbox',
 -                        icon: '/point_of_sale/static/src/img/open-cashbox.png',
 -                        click: function(){
 -                            self.pos.proxy.open_cashbox();
 -                        },
 -                    });
 +        },
 +        // handle both keyboard and numpad input. Accepts
 +        // a string that represents the key pressed.
 +        payment_input: function(input) {
 +            var oldbuf = this.inputbuffer.slice(0);
 +
 +            if (input === '.') {
 +                if (this.firstinput) {
 +                    this.inputbuffer = "0.";
 +                }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
 +                    this.inputbuffer += "0.";
 +                } else if (this.inputbuffer.indexOf('.') < 0){
 +                    this.inputbuffer = this.inputbuffer + '.';
 +                }
 +            } else if (input === 'CLEAR') {
 +                this.inputbuffer = ""; 
 +            } else if (input === 'BACKSPACE') { 
 +                this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
 +            } else if (input === '+') {
 +                if ( this.inputbuffer[0] === '-' ) {
 +                    this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
 +                }
 +            } else if (input === '-') {
 +                if ( this.inputbuffer[0] === '-' ) {
 +                    this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
 +                } else {
 +                    this.inputbuffer = '-' + this.inputbuffer;
 +                }
 +            } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
 +                this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
 +            } else if (!isNaN(parseInt(input))) {
 +                if (this.firstinput) {
 +                    this.inputbuffer = '' + input;
 +                } else {
 +                    this.inputbuffer += input;
 +                }
              }
  
 -            this.update_payment_summary();
 +            this.firstinput = false;
  
 -        },
 -        close: function(){
 -            this._super();
 -            this.disable_numpad();
 -            document.body.removeEventListener('keyup',this.hotkey_handler);
 -        },
 -        remove_empty_lines: function(){
 -            var order = this.pos.get('selectedOrder');
 -            var lines = order.get('paymentLines').models.slice(0);
 -            for(var i = 0; i < lines.length; i++){ 
 -                var line = lines[i];
 -                if(line.get_amount() === 0){
 -                    order.removePaymentline(line);
 +            if (this.inputbuffer !== oldbuf) {
 +                var order = this.pos.get_order();
 +                if (order.selected_paymentline) {
 +                    order.selected_paymentline.set_amount(parseFloat(this.inputbuffer));
 +                    this.order_changes();
 +                    this.render_paymentlines();
 +                    this.$('.paymentline.selected .edit').text(this.inputbuffer);
                  }
              }
          },
 -        back: function() {
 -            this.remove_empty_lines();
 -            this.pos_widget.screen_selector.set_current_screen(this.back_screen);
 +        click_numpad: function(button) {
 +            this.payment_input(button.data('action'));
          },
 -        bind_events: function() {
 -            if(this.old_order){
 -                this.old_order.unbind(null,null,this);
 -            }
 -            var order = this.pos.get('selectedOrder');
 -                order.bind('change:selected_paymentline',this.focus_selected_line,this);
 -
 -            this.old_order = order;
 -
 -            if(this.old_paymentlines){
 -                this.old_paymentlines.unbind(null,null,this);
 -            }
 -            var paymentlines = order.get('paymentLines');
 -                paymentlines.bind('add', this.add_paymentline, this);
 -                paymentlines.bind('change:selected', this.rerender_paymentline, this);
 -                paymentlines.bind('change:amount', function(line){
 -                        if(!line.selected && line.node){
 -                            line.node.value = line.amount.toFixed(this.pos.currency.decimals);
 -                        }
 -                        this.update_payment_summary();
 -                    },this);
 -                paymentlines.bind('remove', this.remove_paymentline, this);
 -                paymentlines.bind('all', this.update_payment_summary, this);
 -
 -            this.old_paymentlines = paymentlines;
 -
 -            if(this.old_orderlines){
 -                this.old_orderlines.unbind(null,null,this);
 +        render_numpad: function() {
 +            var self = this;
 +            var numpad = $(QWeb.render('PaymentScreen-Numpad', { widget:this }));
 +            numpad.on('click','button',function(){
 +                self.click_numpad($(this));
 +            });
 +            return numpad;
 +        },
 +        click_delete_paymentline: function(cid){
 +            var lines = this.pos.get_order().get('paymentLines').models;
 +            for ( var i = 0; i < lines.length; i++ ) {
 +                if (lines[i].cid === cid) {
 +                    this.pos.get_order().removePaymentline(lines[i]);
 +                    this.reset_input();
 +                    this.render_paymentlines();
 +                    return;
 +                }
              }
 -            var orderlines = order.get('orderLines');
 -                orderlines.bind('all', this.update_payment_summary, this);
 -
 -            this.old_orderlines = orderlines;
          },
 -        focus_selected_line: function(){
 -            var line = this.pos.get('selectedOrder').selected_paymentline;
 -            if(line){
 -                var input = line.node.querySelector('input');
 -                if(!input){
 +        click_paymentline: function(cid){
 +            var lines = this.pos.get_order().get('paymentLines').models;
 +            for ( var i = 0; i < lines.length; i++ ) {
 +                if (lines[i].cid === cid) {
 +                    this.pos.get_order().selectPaymentline(lines[i]);
 +                    this.reset_input();
 +                    this.render_paymentlines();
                      return;
                  }
 -                var value = input.value;
 -                input.focus();
 +            }
 +        },
 +        render_paymentlines: function() {
 +            var self  = this;
 +            var order = this.pos.get_order();
 +            var lines = order.get('paymentLines').models;
  
 -                if(this.numpad_state){
 -                    this.numpad_state.reset();
 -                }
 +            this.$('.paymentlines-container').empty();
 +            var lines = $(QWeb.render('PaymentScreen-Paymentlines', { 
 +                widget: this, 
 +                order: order,
 +                paymentlines: lines,
 +            }));
  
 -                if(Number(value) === 0){
 -                    input.value = '';
 -                }else{
 -                    input.value = value;
 -                    input.select();
 +            lines.on('click','.delete-button',function(){
 +                self.click_delete_paymentline($(this).data('cid'));
 +            });
 +
 +            lines.on('click','.paymentline',function(){
 +                self.click_paymentline($(this).data('cid'));
 +            });
 +                
 +            lines.appendTo(this.$('.paymentlines-container'));
 +        },
 +        click_paymentmethods: function(id) {
 +            var cashregister = null;
 +            for ( var i = 0; i < this.pos.cashregisters.length; i++ ) {
 +                if ( this.pos.cashregisters[i].journal_id[0] === id ){
 +                    cashregister = this.pos.cashregisters[i];
 +                    break;
                  }
              }
 +            this.pos.get_order().addPaymentline( cashregister );
 +            this.reset_input();
 +            this.render_paymentlines();
          },
 -        add_paymentline: function(line) {
 -            var list_container = this.el.querySelector('.payment-lines');
 -                list_container.appendChild(this.render_paymentline(line));
 -            
 -            if(this.numpad_state){
 -                this.numpad_state.reset();
 +        render_paymentmethods: function() {
 +            var self = this;
 +            var methods = $(QWeb.render('PaymentScreen-Paymentmethods', { widget:this }));
 +                methods.on('click','.paymentmethod',function(){
 +                    self.click_paymentmethods($(this).data('id'));
 +                });
 +            return methods;
 +        },
 +        click_invoice: function(){
 +            var order = this.pos.get_order();
 +            order.set_to_invoice(!order.is_to_invoice());
 +            if (order.is_to_invoice()) {
 +                this.$('.js_invoice').addClass('highlight');
 +            } else {
 +                this.$('.js_invoice').removeClass('highlight');
              }
          },
 -        render_paymentline: function(line){
 -            var el_html  = openerp.qweb.render('Paymentline',{widget: this, line: line});
 -                el_html  = _.str.trim(el_html);
 +        renderElement: function() {
 +            var self = this;
 +            this._super();
  
 -            var el_node  = document.createElement('tbody');
 -                el_node.innerHTML = el_html;
 -                el_node = el_node.childNodes[0];
 -                el_node.line = line;
 -                el_node.querySelector('.paymentline-delete')
 -                    .addEventListener('click', this.line_delete_handler);
 -                el_node.addEventListener('click', this.line_click_handler);
 -                el_node.querySelector('input')
 -                    .addEventListener('keyup', this.line_change_handler);
 +            var numpad = this.render_numpad();
 +            numpad.appendTo(this.$('.payment-numpad'));
  
 -            line.node = el_node;
 +            var methods = this.render_paymentmethods();
 +            methods.appendTo(this.$('.paymentmethods-container'));
 +
 +            this.render_paymentlines();
 +
 +            this.$('.back').click(function(){
 +                self.pos_widget.screen_selector.back();
 +            });
 +
 +            this.$('.next').click(function(){
 +                self.validate_order();
 +            });
 +
 +            this.$('.js_invoice').click(function(){
 +                self.click_invoice();
 +            });
  
 -            return el_node;
 -        },
 -        rerender_paymentline: function(line){
 -            var old_node = line.node;
 -            var new_node = this.render_paymentline(line);
 -            
 -            old_node.parentNode.replaceChild(new_node,old_node);
          },
 -        remove_paymentline: function(line){
 -            line.node.parentNode.removeChild(line.node);
 -            line.node = undefined;
 +        show: function(){
 +            this.pos.get_order().clean_empty_paymentlines();
 +            this.reset_input();
 +            this.render_paymentlines();
 +            this.order_changes();
 +            window.document.body.addEventListener('keydown',this.keyboard_handler);
 +            this._super();
          },
 -        renderElement: function(){
 +        hide: function(){
 +            window.document.body.removeEventListener('keydown',this.keyboard_handler);
              this._super();
 -
 -            var paymentlines   = this.pos.get('selectedOrder').get('paymentLines').models;
 -            var list_container = this.el.querySelector('.payment-lines');
 -
 -            for(var i = 0; i < paymentlines.length; i++){
 -                list_container.appendChild(this.render_paymentline(paymentlines[i]));
 -            }
 -            
 -            this.update_payment_summary();
 -        },
 -        update_payment_summary: function() {
 -            var currentOrder = this.pos.get('selectedOrder');
 -            var paidTotal = currentOrder.getPaidTotal();
 -            var dueTotal = currentOrder.getTotalTaxIncluded();
 -            var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
 -            var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
 -
 -            this.$('.payment-due-total').html(this.format_currency(dueTotal));
 -            this.$('.payment-paid-total').html(this.format_currency(paidTotal));
 -            this.$('.payment-remaining').html(this.format_currency(remaining));
 -            this.$('.payment-change').html(this.format_currency(change));
 -            if(currentOrder.selected_orderline === undefined){
 -                remaining = 1;  // What is this ? 
 -            }
 -                
 -            if(this.pos_widget.action_bar){
 -                this.pos_widget.action_bar.set_button_disabled('validation', !this.is_paid());
 -                this.pos_widget.action_bar.set_button_disabled('invoice', !this.is_paid());
 +        },
 +        // sets up listeners to watch for order changes
 +        watch_order_changes: function() {
 +            var self = this;
 +            var order = this.pos.get_order();
 +            if(this.old_order){
 +                this.old_order.unbind(null,null,this);
              }
 +            order.bind('all',function(){
 +                self.order_changes();
 +            });
 +            this.old_order = order;
          },
 -        is_paid: function(){
 -            var currentOrder = this.pos.get('selectedOrder');
 -            return (currentOrder.getTotalTaxIncluded() < 0.000001 
 -                   || currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
 -
 +        // called when the order is changed, used to show if
 +        // the order is paid or not
 +        order_changes: function(){
 +            var self = this;
 +            var order = this.pos.get_order();
 +            if (order.isPaid()) {
 +                self.$('.next').addClass('highlight');
 +            }else{
 +                self.$('.next').removeClass('highlight');
 +            }
          },
 -        validate_order: function(options) {
 +        // Check if the order is paid, then sends it to the backend,
 +        // and complete the sale process
 +        validate_order: function() {
              var self = this;
 -            options = options || {};
  
 -            var currentOrder = this.pos.get('selectedOrder');
 +            var order = this.pos.get_order();
  
 -            if(currentOrder.get('orderLines').models.length === 0){
 +            if(order.get('orderLines').models.length === 0){
                  this.pos_widget.screen_selector.show_popup('error',{
                      'message': _t('Empty Order'),
                      'comment': _t('There must be at least one product in your order before it can be validated'),
                  return;
              }
  
 -            if(!this.is_paid()){
 +            if (!order.isPaid() || this.invoicing) {
                  return;
              }
  
              // The exact amount must be paid if there is no cash payment method defined.
 -            if (Math.abs(currentOrder.getTotalTaxIncluded() - currentOrder.getPaidTotal()) > 0.00001) {
 +            if (Math.abs(order.getTotalTaxIncluded() - order.getPaidTotal()) > 0.00001) {
                  var cash = false;
                  for (var i = 0; i < this.pos.cashregisters.length; i++) {
                      cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
                  }
              }
  
 -            if (this.pos.config.iface_cashdrawer) {
 +            if (order.isPaidWithCash() && this.pos.config.iface_cashdrawer) { 
 +            
                      this.pos.proxy.open_cashbox();
              }
  
 -            if(options.invoice){
 -                // deactivate the validation button while we try to send the order
 -                this.pos_widget.action_bar.set_button_disabled('validation',true);
 -                this.pos_widget.action_bar.set_button_disabled('invoice',true);
 -
 -                var invoiced = this.pos.push_and_invoice_order(currentOrder);
 +            if (order.is_to_invoice()) {
 +                var invoiced = this.pos.push_and_invoice_order(order);
 +                this.invoicing = true;
  
                  invoiced.fail(function(error){
 -                    if(error === 'error-no-client'){
 -                        self.pos_widget.screen_selector.show_popup('error',{
 -                            message: _t('An anonymous order cannot be invoiced'),
 -                            comment: _t('Please select a client for this order. This can be done by clicking the order tab'),
 +                    self.invoicing = false;
 +                    if (error === 'error-no-client') {
 +                        self.pos_widget.screen_selector.show_popup('confirm',{
 +                            message: _t('Please select the Customer'),
 +                            comment: _t('You need to select the customer before you can invoice an order.'),
 +                            confirm: function(){
 +                                self.pos_widget.screen_selector.set_current_screen('clientlist');
 +                            },
                          });
 -                    }else{
 +                    } else {
                          self.pos_widget.screen_selector.show_popup('error',{
                              message: _t('The order could not be sent'),
                              comment: _t('Check your internet connection and try again.'),
                          });
                      }
                  });
  
                  invoiced.done(function(){
 -                    self.pos_widget.action_bar.set_button_disabled('validation',false);
 -                    self.pos_widget.action_bar.set_button_disabled('invoice',false);
 -                    self.pos.get('selectedOrder').destroy();
 +                    self.invoicing = false;
 +                    order.finalize();
                  });
 -
 -            }else{
 -                this.pos.push_order(currentOrder) 
 -                if(this.pos.config.iface_print_via_proxy){
 +            } else {
 +                this.pos.push_order(order) 
 +                if (this.pos.config.iface_print_via_proxy) {
                      var receipt = currentOrder.export_for_printing();
                      this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{
                          receipt: receipt, widget: self,
                      }));
 -                    this.pos.get('selectedOrder').destroy();    //finish order and go back to scan screen
 -                }else{
 +                    order.finalize();    //finish order and go back to scan screen
 +                } else {
                      this.pos_widget.screen_selector.set_current_screen(this.next_screen);
                  }
              }
 -
 -            // hide onscreen (iOS) keyboard 
 -            setTimeout(function(){
 -                document.activeElement.blur();
 -                $("input").blur();
 -            },250);
 -        },
 -        enable_numpad: function(){
 -            this.disable_numpad();  //ensure we don't register the callbacks twice
 -            this.numpad_state = this.pos_widget.numpad.state;
 -            if(this.numpad_state){
 -                this.numpad_state.reset();
 -                this.numpad_state.changeMode('payment');
 -                this.numpad_state.bind('set_value',   this.set_value, this);
 -                this.numpad_state.bind('change:mode', this.set_mode_back_to_payment, this);
 -            }
 -                    
 -        },
 -        disable_numpad: function(){
 -            if(this.numpad_state){
 -                this.numpad_state.unbind('set_value',  this.set_value);
 -                this.numpad_state.unbind('change:mode',this.set_mode_back_to_payment);
 -            }
 -        },
 -      set_mode_back_to_payment: function() {
 -              this.numpad_state.set({mode: 'payment'});
 -      },
 -        set_value: function(val) {
 -            var selected_line =this.pos.get('selectedOrder').selected_paymentline;
 -            if(selected_line){
 -                selected_line.set_amount(val);
 -                selected_line.node.querySelector('input').value = selected_line.amount.toFixed(2);
 -            }
          },
      });
 +
  }
@@@ -83,19 -83,44 +83,19 @@@ function openerp_pos_widgets(instance, 
          },
      });
  
 -    // The paypad allows to select the payment method (cashregisters) 
 -    // used to pay the order.
 -    module.PaypadWidget = module.PosBaseWidget.extend({
 -        template: 'PaypadWidget',
 +    // The action pads contains the payment button and the customer selection button.
 +    module.ActionpadWidget = module.PosBaseWidget.extend({
 +        template: 'ActionpadWidget',
          renderElement: function() {
              var self = this;
              this._super();
 -
 -            _.each(this.pos.cashregisters,function(cashregister) {
 -                var button = new module.PaypadButtonWidget(self,{
 -                    pos: self.pos,
 -                    pos_widget : self.pos_widget,
 -                    cashregister: cashregister,
 -                });
 -                button.appendTo(self.$el);
 +            this.$('.pay').click(function(){
 +                self.pos.pos_widget.screen_selector.set_current_screen('payment');
              });
 -        }
 -    });
 -
 -    module.PaypadButtonWidget = module.PosBaseWidget.extend({
 -        template: 'PaypadButtonWidget',
 -        init: function(parent, options){
 -            this._super(parent, options);
 -            this.cashregister = options.cashregister;
 -        },
 -        renderElement: function() {
 -            var self = this;
 -            this._super();
 -
 -            this.$el.click(function(){
 -                if (self.pos.get('selectedOrder').get('screen') === 'receipt'){  //TODO Why ?
 -                    console.warn('TODO should not get there...?');
 -                    return;
 -                }
 -                self.pos.get('selectedOrder').addPaymentline(self.cashregister);
 -                self.pos_widget.screen_selector.set_current_screen('payment');
 +            this.$('.set-customer').click(function(){
 +                self.pos.pos_widget.screen_selector.set_current_screen('clientlist');
              });
 -        },
 +        }
      });
  
      module.OrderWidget = module.PosBaseWidget.extend({
          },
      });
  
 -    module.ActionButtonWidget = instance.web.Widget.extend({
 -        template:'ActionButtonWidget',
 -        icon_template:'ActionButtonWidgetWithIcon',
 -        init: function(parent, options){
 -            this._super(parent, options);
 -            this.label = options.label || 'button';
 -            this.rightalign = options.rightalign || false;
 -            this.click_action = options.click;
 -            this.disabled = options.disabled || false;
 -            if(options.icon){
 -                this.icon = options.icon;
 -                this.template = this.icon_template;
 -            }
 -        },
 -        set_disabled: function(disabled){
 -            if(this.disabled != disabled){
 -                this.disabled = !!disabled;
 -                this.renderElement();
 -            }
 -        },
 -        renderElement: function(){
 -            this._super();
 -            if(this.click_action && !this.disabled){
 -                this.$el.click(_.bind(this.click_action, this));
 -            }
 -        },
 -    });
 -
 -    module.ActionBarWidget = instance.web.Widget.extend({
 -        template:'ActionBarWidget',
 -        init: function(parent, options){
 -            this._super(parent,options);
 -            this.button_list = [];
 -            this.buttons = {};
 -            this.visibility = {};
 -        },
 -        set_element_visible: function(element, visible, action){
 -            if(visible != this.visibility[element]){
 -                this.visibility[element] = !!visible;
 -                if(visible){
 -                    this.$('.'+element).removeClass('oe_hidden');
 -                }else{
 -                    this.$('.'+element).addClass('oe_hidden');
 -                }
 -            }
 -            if(visible && action){
 -                this.action[element] = action;
 -                this.$('.'+element).off('click').click(action);
 -            }
 -        },
 -        set_button_disabled: function(name, disabled){
 -            var b = this.buttons[name];
 -            if(b){
 -                b.set_disabled(disabled);
 -            }
 -        },
 -        destroy_buttons:function(){
 -            for(var i = 0; i < this.button_list.length; i++){
 -                this.button_list[i].destroy();
 -            }
 -            this.button_list = [];
 -            this.buttons = {};
 -            return this;
 -        },
 -        get_button_count: function(){
 -            return this.button_list.length;
 -        },
 -        add_new_button: function(button_options){
 -            var button = new module.ActionButtonWidget(this,button_options);
 -            this.button_list.push(button);
 -            if(button_options.name){
 -                this.buttons[button_options.name] = button;
 -            }
 -            button.appendTo(this.$('.pos-actionbar-button-list'));
 -            return button;
 -        },
 -        show:function(){
 -            this.$el.removeClass('oe_hidden');
 -        },
 -        hide:function(){
 -            this.$el.addClass('oe_hidden');
 -        },
 -    });
 -
      module.ProductCategoriesWidget = module.PosBaseWidget.extend({
          template: 'ProductCategoriesWidget',
          init: function(parent, options){
      // - a header, containing the list of orders
      // - a leftpane, containing the list of bought products (orderlines) 
      // - a rightpane, containing the screens (see pos_screens.js)
 -    // - an actionbar on the bottom, containing various action buttons
      // - popups
      // - an onscreen keyboard
      // a screen_selector which controls the switching between screens and the showing/closing of popups
  
                  instance.webclient.set_content_full_screen(true);
  
 +                if(self.pos.config.iface_fullscreen && document.body.webkitRequestFullscreen && (
 +                    window.screen.availWidth  > window.innerWidth ||
 +                    window.screen.availHeight > window.innerHeight    )){
 +                    self.screen_selector.show_popup('fullscreen');
 +                }
                  self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
  
                  self.pos.push_order();
              this.confirm_popup = new module.ConfirmPopupWidget(this,{});
              this.confirm_popup.appendTo(this.$el);
  
 +            this.fullscreen_popup = new module.FullscreenPopup(this,{});
 +            this.fullscreen_popup.appendTo(this.$el);
 +
              this.unsent_orders_popup = new module.UnsentOrdersPopupWidget(this,{});
              this.unsent_orders_popup.appendTo(this.$el);
  
              this.username   = new module.UsernameWidget(this,{});
              this.username.replace(this.$('.placeholder-UsernameWidget'));
  
 -            this.action_bar = new module.ActionBarWidget(this);
 -            this.action_bar.replace(this.$(".placeholder-RightActionBar"));
 -
 -            this.paypad = new module.PaypadWidget(this, {});
 -            this.paypad.replace(this.$('.placeholder-PaypadWidget'));
 +            this.actionpad = new module.ActionpadWidget(this, {});
 +            this.actionpad.replace(this.$('.placeholder-ActionpadWidget'));
  
              this.numpad = new module.NumpadWidget(this);
              this.numpad.replace(this.$('.placeholder-NumpadWidget'));
                      'error-barcode': this.error_barcode_popup,
                      'error-traceback': this.error_traceback_popup,
                      'confirm': this.confirm_popup,
 +                    'fullscreen': this.fullscreen_popup,
                      'unsent-orders': this.unsent_orders_popup,
                  },
                  default_screen: 'products',
                  this.numpad_visible = visible;
                  if(visible){
                      this.numpad.show();
 -                    this.paypad.show();
 +                    this.actionpad.show();
                  }else{
                      this.numpad.hide();
 -                    this.paypad.hide();
 +                    this.actionpad.hide();
                  }
              }
          },
                  self.pos.push_order().then(function(){
                      return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(function(res) {
                          window.location = '/web#action=' + res[0]['res_id'];
+                     },function(err,event) {
+                         event.preventDefault();
+                         self.screen_selector.show_popup('error',{
+                             'message': _t('Could not close the point of sale.'),
+                             'comment': _t('Your internet connection is probably down.'),
+                         });
+                         self.close_button.renderElement();
                      });
                  });
              }
@@@ -5,7 -5,7 +5,7 @@@
          <menuitem id="base.menu_sale_config_sales" name="Sales"
              parent="base.menu_base_config" sequence="0"/>
          <menuitem id="menu_product_pricelist_main" name="Pricelists" 
 -            parent="base.menu_base_config" groups="product.group_sale_pricelist" sequence="70"/>
 +            parent="base.menu_base_config" groups="product.group_sale_pricelist" sequence="3"/>
  
          <record id="product_pricelist_version_form_view" model="ir.ui.view">
              <field name="name">product.pricelist.version.form</field>
                          <field name="min_quantity"/>
                          <field name="sequence"/>
                      </group>
-                     <group col="4" string="Price Computation">
+                     <separator string="Price Computation"/>
+                     <div class="oe_grey" groups="product.group_uom">
+                         <p>The computed price is expressed in the default Unit of Measure of the product.</p>
+                     </div>
+                     <group col="4">
                          <field name="base"/>
                          <field name="base_pricelist_id" attrs="{'invisible':[('base', '!=', -1)],'required': [('base','=', -1)], 'readonly': [('base','!=', -1)]}"/>
                      </group>
@@@ -32,7 -32,7 +32,7 @@@ from openerp.tools import DEFAULT_SERVE
  import psycopg2
  
  import openerp.addons.decimal_precision as dp
- from openerp.tools.float_utils import float_round
+ from openerp.tools.float_utils import float_round, float_compare
  
  def ean_checksum(eancode):
      """returns the checksum of an ean string of length 13, returns -1 if the string has the wrong length"""
@@@ -154,6 -154,7 +154,7 @@@ class product_uom(osv.osv)
      _defaults = {
          'active': 1,
          'rounding': 0.01,
+         'factor': 1,
          'uom_type': 'reference',
      }
  
                  raise osv.except_osv(_('Error!'), _('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!.') % (from_unit.name,to_unit.name,))
              else:
                  return qty
-         # First round to the precision of the original unit, so that
-         # float representation errors do not bias the following ceil()
-         # e.g. with 1 / (1/12) we could get 12.0000048, ceiling to 13! 
-         amount = float_round(qty/from_unit.factor, precision_rounding=from_unit.rounding)
+         amount = qty/from_unit.factor
          if to_unit:
              amount = amount * to_unit.factor
              if round:
@@@ -519,7 -517,8 +517,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'),
          for tmpl_id in tmpl_ids:
  
              # list of values combination
+             variant_alone = []
              all_variants = [[]]
              for variant_id in tmpl_id.attribute_line_ids:
-                 if len(variant_id.value_ids) > 1:
-                     temp_variants = []
+                 if len(variant_id.value_ids) == 1:
+                     variant_alone.append(variant_id.value_ids[0])
+                 temp_variants = []
+                 for variant in all_variants:
                      for value_id in variant_id.value_ids:
-                         for variant in all_variants:
-                             temp_variants.append(variant + [int(value_id)])
-                     all_variants = temp_variants
+                         temp_variants.append(variant + [int(value_id)])
+                 all_variants = temp_variants
+             # adding an attribute with only one value should not recreate product
+             # write this attribute on every product to make sure we don't lose them
+             for variant_id in variant_alone:
+                 product_ids = []
+                 for product_id in tmpl_id.product_variant_ids:
+                     if variant_id.id not in map(int, product_id.attribute_value_ids):
+                         product_ids.append(product_id.id)
+                 product_obj.write(cr, uid, product_ids, {'attribute_value_ids': [(4, variant_id.id)]}, context=ctx)
  
              # check product
              variant_ids_to_active = []
          ''' 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)
@@@ -1042,8 -1058,6 +1051,8 @@@ class product_product(osv.osv)
          return result
  
      def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
 +        if context is None:
 +            context = {}
          if not args:
              args = []
          if name:
                  res = ptrn.search(name)
                  if res:
                      ids = self.search(cr, user, [('default_code','=', res.group(2))] + args, limit=limit, context=context)
 +            # still no results, partner in context: search on supplier info as last hope to find something
 +            if not ids and context.get('partner_id'):
 +                supplier_ids = self.pool['product.supplierinfo'].search(
 +                    cr, user, [
 +                        ('name', '=', context.get('partner_id')),
 +                        '|',
 +                        ('product_code', operator, name),
 +                        ('product_name', operator, name)
 +                    ], context=context)
 +                if supplier_ids:
 +                    ids = self.search(cr, user, [('product_tmpl_id.seller_ids', 'in', supplier_ids)], limit=limit, context=context)
          else:
              ids = self.search(cr, user, args, limit=limit, context=context)
          result = self.name_get(cr, user, ids, context=context)
@@@ -1247,7 -1250,7 +1256,7 @@@ class product_supplierinfo(osv.osv)
          'product_tmpl_id' : fields.many2one('product.template', 'Product Template', required=True, ondelete='cascade', select=True, oldname='product_id'),
          'delay' : fields.integer('Delivery Lead Time', required=True, help="Lead time in days between the confirmation of the purchase order and the receipt of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning."),
          'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist', copy=True),
 -        'company_id':fields.many2one('res.company','Company',select=1),
 +        'company_id':fields.many2one('res.company', string='Company',select=1),
      }
      _defaults = {
          'min_qty': 0.0,
@@@ -1280,7 -1283,7 +1289,7 @@@ class res_currency(osv.osv)
              main_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
              for currency_id in ids:
                  if currency_id == main_currency.id:
-                     if main_currency.rounding < 10 ** -digits:
+                     if float_compare(main_currency.rounding, 10 ** -digits, precision_digits=6) == -1:
                          return False
          return True
  
@@@ -1299,7 -1302,7 +1308,7 @@@ class decimal_precision(osv.osv)
              main_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
              for decimal_precision in ids:
                  if decimal_precision == account_precision_id:
-                     if main_currency.rounding < 10 ** -digits:
+                     if float_compare(main_currency.rounding, 10 ** -digits, precision_digits=6) == -1:
                          return False
          return True
  
@@@ -10,8 -10,8 +10,8 @@@
              <field name="model">product.template</field>
              <field name="arch" type="xml">
                  <search string="Product">
 -                    <field name="name" string="Product"/>
 -                    <filter string="Services" icon="terp-accessories-archiver" domain="[('type','=','service')]"/>
 +                    <field name="name" string="Product" filter_domain="['|',('default_code','ilike',self),('name','ilike',self)]"/>
 +                    <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)]"/>
                              <div class="oe_title" style="width: 390px;">
                                  <label class="oe_edit_only" for="name" string="Product Name"/>
                                  <h1><field name="name" class="oe_inline"/></h1>
 -                            </div>
 -                            <div class="oe_left" name="options" groups="base.group_user">
 -                                <div>
 -                                    <field name="sale_ok"/>
 -                                    <label for="sale_ok"/>
 +                                <div name="options" groups="base.group_user">
 +                                    <div>
 +                                        <field name="sale_ok"/>
 +                                        <label for="sale_ok"/>
 +                                    </div>
                                  </div>
                              </div>
                          </div>
              <field name="mode">primary</field>
              <field name="inherit_id" ref="product.product_template_search_view"/>
              <field name="arch" type="xml">
 -                <field name="name" position="replace">
 -                   <field name="name" string="Product" filter_domain="['|',('default_code','ilike',self),('name','ilike',self)]"/>
 -                </field>
                  <field name="product_variant_ids" position="replace">
                      <field name="attribute_value_ids"/>
                  </field>
                <p class="oe_view_nocontent_create">
                  Click to define a new product.
                </p><p>
 -                You must define a product for everything you buy or sell,
 -                whether it's a physical product, a consumable or service.
 +                You must define a product for everything you sell, whether it's
 +                a physical product, a consumable or a service you offer to
 +                customers.
 +              </p><p>
 +                The product form contains information to simplify the sale
 +                process: price, notes in the quotation, accounting data,
 +                procurement methods, etc.
                </p>
              </field>
          </record>
                <p class="oe_view_nocontent_create">
                  Click to define a new product.
                </p><p>
 -                You must define a product for everything you buy or sell,
 -                whether it's a physical product, a consumable or service.
 +                You must define a product for everything you sell, whether it's
 +                a physical product, a consumable or a service you offer to
 +                customers.
 +              </p><p>
 +                The product form contains information to simplify the sale
 +                process: price, notes in the quotation, accounting data,
 +                procurement methods, etc.
                </p>
              </field>
          </record>
              <field name="view_type">form</field>
              <field name="view_id" ref="product_template_kanban_view"/>
              <field name="context">{"search_default_filter_to_sell":1}</field>
 +            <field name="help" type="html">
 +                <p class="oe_view_nocontent_create">
 +                    Click to define a new product.
 +                </p><p>
 +                    You must define a product for everything you sell, whether it's a physical product, a consumable or a service you offer to  customers.               
 +                </p><p>
 +                    The product form contains information to simplify the sale process: price, notes in the quotation, accounting data, procurement methods, etc.
 +                </p>
 +            </field>
          </record>
  
          <menuitem action="product_template_action"
  
          <!-- 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="factor"
                                      digits="[42,5]"
                                      attrs="{'invisible':[('uom_type','!=','smaller')],
-                                             'readonly':[('uom_type','!=','smaller')]}"/>
+                                             'readonly':[('uom_type','=','bigger')]}"/>
                                  <field name="factor_inv"
                                      digits="[42,5]"
                                      attrs="{'invisible':[('uom_type','!=','bigger')],
              </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>
@@@ -38,9 -38,9 +38,9 @@@ class project_project(osv.osv)
                  factor_id = data_obj.browse(cr, uid, data_id).res_id
                  res['value'].update({'to_invoice': factor_id})
          return res
 -
 +        
      _defaults = {
 -        'use_timesheets': True,
 +        'invoice_on_timesheets': True,
      }
  
      def open_timesheets(self, cr, uid, ids, context=None):
@@@ -118,7 -118,9 +118,9 @@@ class project_work(osv.osv)
          vals_line['user_id'] = vals['user_id']
          vals_line['product_id'] = result['product_id']
          if vals.get('date'):
-             vals_line['date' ] = vals['date'][:10]
+             timestamp = datetime.datetime.strptime(vals['date'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
+             ts = fields.datetime.context_timestamp(cr, uid, timestamp, context)
+             vals_line['date'] = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
  
          # Calculate quantity based on employee's product's uom
          vals_line['unit_amount'] = vals['hours']
@@@ -153,11 -153,6 +153,6 @@@ class purchase_order(osv.osv)
          type_obj = self.pool.get('stock.picking.type')
          user_obj = self.pool.get('res.users')
          company_id = user_obj.browse(cr, uid, uid, context=context).company_id.id
-         pick_type = obj_data.get_object_reference(cr, uid, 'stock', 'picking_type_in') and obj_data.get_object_reference(cr, uid, 'stock', 'picking_type_in')[1] or False
-         if pick_type:
-             type = type_obj.browse(cr, uid, pick_type, context=context)
-             if type and type.warehouse_id and type.warehouse_id.company_id.id == company_id:
-                 return pick_type
          types = type_obj.search(cr, uid, [('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)], context=context)
          if not types:
              types = type_obj.search(cr, uid, [('code', '=', 'incoming'), ('warehouse_id', '=', False)], context=context)
                                      help="It indicates that an invoice has been validated"),
          'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
          'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True,
 -            readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
 +            readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)],'bid':[('readonly',False)]},
              help="Based on Purchase Order lines: place individual lines in 'Invoice Control / On Purchase Order lines' from where you can selectively create an invoice.\n" \
                  "Based on generated invoice: create a draft invoice you can validate later.\n" \
                  "Based on incoming shipments: let you create an invoice when receipts are validated."
  
      def create(self, cr, uid, vals, context=None):
          if vals.get('name','/')=='/':
 -            vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
 +            vals['name'] = self.pool.get('ir.sequence').next_by_code(cr, uid, 'purchase.order') or '/'
          context = dict(context or {}, mail_create_nolog=True)
          order =  super(purchase_order, self).create(cr, uid, vals, context=context)
          self.message_post(cr, uid, [order], body=_("RFQ created"), context=context)
@@@ -1358,7 -1353,7 +1353,7 @@@ class procurement_order(osv.osv)
                          po_line_id = po_line_obj.create(cr, SUPERUSER_ID, line_vals, context=context)
                          linked_po_ids.append(procurement.id)
                  else:
 -                    name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
 +                    name = seq_obj.next_by_code(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
                      po_vals = {
                          'name': name,
                          'origin': procurement.origin,
@@@ -47,7 -47,7 +47,7 @@@
          <menuitem
              action="purchase_pricelist_version_action" id="menu_purchase_pricelist_version_action"
              parent="menu_purchase_config_pricelist" sequence="2" groups="product.group_purchase_pricelist"/>
 -        
 +
          <menuitem
              action="product.product_price_type_action" id="menu_product_pricelist_action2_purchase_type"
              parent="menu_purchase_config_pricelist" sequence="60" />
  
          <menuitem
               action="product.product_uom_categ_form_action" id="menu_purchase_uom_categ_form_action"
 -             parent="purchase.menu_product_in_config_purchase" sequence="5" />
 +             parent="purchase.menu_product_in_config_purchase" sequence="10" />
  
          <menuitem
                action="product.product_uom_form_action" id="menu_purchase_uom_form_action"
 -              parent="purchase.menu_product_in_config_purchase" sequence="10"/>
 -
 -        <menuitem
 -            id="menu_purchase_partner_cat" name="Address Book"
 -            parent="menu_purchase_config_purchase"/>
 -
 -        <menuitem
 -            action="base.action_partner_category_form" id="menu_partner_categories_in_form" name="Partner Tags"
 -            parent="purchase.menu_purchase_partner_cat" groups="base.group_no_one"/>
 +              parent="purchase.menu_product_in_config_purchase" sequence="5"/>
  
      <!--Supplier menu-->
      <menuitem id="base.menu_procurement_management_supplier_name" name="Suppliers"
            parent="base.menu_purchase_root" sequence="8"/>
  
        <menuitem name="Products by Category" id="menu_product_by_category_purchase_form" action="product.product_category_action"
 -           parent="menu_procurement_management_product" sequence="10"/>
 +           parent="menu_procurement_management_product" sequence="2"/>
  
        <menuitem name="Products" id="menu_procurement_partner_contact_form" action="product_normal_action_puchased"
 -          parent="menu_procurement_management_product"/>
 +          parent="menu_procurement_management_product" sequence="1"/>
  
          <record model="ir.ui.view" id="purchase_order_calendar">
              <field name="name">purchase.order.calendar</field>
                          <page string="Products">
                              <field name="order_line">
                                  <tree string="Purchase Order Lines" editable="bottom">
 -                                    <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
 +                                    <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)" context="{'partner_id': parent.partner_id}"/>
                                      <field name="name"/>
                                      <field name="date_planned"/>
                                      <field name="company_id" groups="base.group_multi_company" widget="selection"/>
 -                                    <field name="account_analytic_id" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
 +                                    <field name="account_analytic_id" context="{'default_partner_id':parent.partner_id}" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
                                      <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
                                      <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
                                      <field name="price_unit"/>
                <p class="oe_view_nocontent_create">
                  Click to create a request for quotation.
                </p><p>
-                 The quotation contains the history of the discussion/negociation
+                 The quotation contains the history of the discussion/negotiation
                  you had with your supplier. Once confirmed, a request for
                  quotation is converted into a purchase order.
                </p><p>
                      <sheet>
                          <group>
                              <group>
 -                                <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)"/>
 +                                <field name="product_id"
 +                                    on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,context)" context="{'partner_id': parent.partner_id}"/>
                                  <label for="product_qty"/>
                                  <div>
                                      <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)" class="oe_inline"/>
diff --combined addons/stock/product.py
@@@ -114,8 -114,8 +114,8 @@@ class product_product(osv.osv)
  
          domain_products = [('product_id', 'in', ids)]
          domain_quant, domain_move_in, domain_move_out = self._get_domain_locations(cr, uid, ids, context=context)
-         domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel'))] + domain_products
-         domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel'))] + domain_products
+         domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
+         domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
          domain_quant += domain_products
          if context.get('lot_id') or context.get('owner_id') or context.get('package_id'):
              if context.get('lot_id'):
      _columns = {
          'reception_count': fields.function(_stock_move_count, string="Receipt", type='integer', multi='pickings'),
          'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'),
 -        'qty_available_text': fields.function(_product_available_text, type='char'),
          'qty_available': fields.function(_product_available, multi='qty_available',
              type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
              string='Quantity On Hand',
                   "or any of its children.\n"
                   "Otherwise, this includes goods stored in any Stock Location "
                   "with 'internal' type."),
 +        'qty_available2': fields.related('qty_available', type="float", relation="product.product", string="On Hand"),
          'virtual_available': fields.function(_product_available, multi='qty_available',
              type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
              string='Forecast Quantity',
@@@ -334,7 -334,7 +334,7 @@@ class product_template(osv.osv)
  
      _columns = {
          'type': fields.selection([('product', 'Stockable Product'), ('consu', 'Consumable'), ('service', 'Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."),
 -        'qty_available_text': fields.function(_product_available_text, type='char'),
 +        'qty_available2': fields.related('qty_available', type="float", relation="product.template", string="On Hand"),
          'property_stock_procurement': fields.property(
              type='many2one',
              relation='stock.location',
              result['context'] = "{'tree_view_ref':'stock.view_move_tree'}"
          return result
  
 +    def write(self, cr, uid, ids, vals, context=None):
 +        if 'uom_po_id' in vals:
 +            product_ids = self.pool.get('product.product').search(cr, uid, [('product_tmpl_id', 'in', ids)], context=context)
 +            if self.pool.get('stock.move').search(cr, uid, [('product_id', 'in', product_ids)], context=context, limit=1):
 +                raise osv.except_osv(_('Error!'), _("You can not change the unit of measure of a product that has already been used in a stock move. If you need to change the unit of measure, you may deactivate this product.") % ())
 +        return super(product_template, self).write(cr, uid, ids, vals, context=context)
 +
  
  class product_removal_strategy(osv.osv):
      _name = 'product.removal'
@@@ -28,6 -28,7 +28,6 @@@
      list_price: 100.0
      standard_price: 70.0
      uom_id: product.product_uom_kgm
 -    uom_po_id: product.product_uom_kgm
      property_stock_inventory: location_inventory
      description: Ice cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
  -
  -
   !record {model: stock.picking, id: incomming_shipment}:
      picking_type_id: stock.picking_type_in
-     origin: 'incomming_shipment for test'
+     origin: 'incoming_shipment for test'
  -
   !record {model: stock.move, id: incomming_shipment_icecream}:
      picking_id: stock.incomming_shipment
  -
   !record {model: stock.picking, id: incomming_shipment1}:
      picking_type_id: stock.picking_type_in
-     origin: 'incomming_shipment'
+     origin: 'incoming_shipment'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_48
  -
   !record {model: stock.picking, id: incomming_shipment2}:
      picking_type_id: stock.picking_type_in
-     origin: 'incomming_shipment main_warehouse'
+     origin: 'incoming_shipment main_warehouse'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_22
  -
   !record {model: stock.picking, id: incomming_shipment3}:
      picking_type_id: stock.picking_type_in
-     origin: 'incomming_shipment your_company warehouse'
+     origin: 'incoming_shipment your_company warehouse'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_10
  -
   !record {model: stock.picking, id: incomming_shipment4}:
      picking_type_id: stock.picking_type_in
-     origin: 'incomming_shipment_main_warehouse'
+     origin: 'incoming_shipment_main_warehouse'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_31
  -
   !record {model: stock.picking, id: incomming_chicago_warehouse}:
      picking_type_id: chi_picking_type_in
-     origin: 'incomming_chicago_warehouse'
+     origin: 'incoming_chicago_warehouse'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_9
  -
   !record {model: stock.picking, id: incomming_chicago_warehouse1}:
      picking_type_id: chi_picking_type_in
-     origin: 'incomming_shipment_chicago_warehouse'
+     origin: 'incoming_shipment_chicago_warehouse'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_48
  -
   !record {model: stock.picking, id: incomming_chicago_warehouse2}:
      picking_type_id: chi_picking_type_in
-     origin: 'incomming_shipment chicago_warehouse'
+     origin: 'incoming_shipment chicago_warehouse'
      partner_id: base.res_partner_6
      move_lines:
        - product_id: product.product_product_32
          <menuitem id="menu_stock_warehouse_mgmt" name="Operations" parent="menu_stock_root" sequence="1"/>
          <menuitem id="menu_stock_product" name="Products" parent="menu_stock_root" sequence="6"/>
          <menuitem name="Products by Category" id="menu_product_by_category_stock_form" action="product.product_category_action"
 -            parent="stock.menu_stock_product" sequence="0" groups="base.group_no_one"/>
 +            parent="stock.menu_stock_product" sequence="2" groups="base.group_no_one"/>
          <menuitem action="product.product_template_action" id="menu_stock_products_menu" parent="menu_stock_product" sequence="1"/>
          <menuitem id="menu_stock_configuration" name="Configuration" parent="menu_stock_root" sequence="15" groups="group_stock_manager"/>
 +        <menuitem id="menu_warehouse_config" name="Warehouse Management" parent="menu_stock_configuration" sequence="1" groups="base.group_no_one"/>
 +        <menuitem id="menu_schedulers_config" name="Schedulers Management" parent="stock.menu_stock_configuration" sequence="2" groups="base.group_no_one"/>
          <menuitem id="menu_stock_inventory_control" name="Inventory Control" parent="menu_stock_root" sequence="2"/>
          <menuitem
              id="menu_product_in_config_stock" name="Products"
 -            parent="stock.menu_stock_configuration" sequence="45" groups="base.group_no_one"/>
 +            parent="stock.menu_stock_configuration" sequence="3" groups="base.group_no_one"/>
          <menuitem
              action="product.product_category_action_form" id="menu_product_category_config_stock"
 -            parent="stock.menu_product_in_config_stock" sequence="0"/>
 +            parent="stock.menu_product_in_config_stock" sequence="2"/>
          <menuitem
              action="product.product_normal_action" id="menu_product_variant_config_stock"
 -            parent="stock.menu_product_in_config_stock" sequence="2"/>
 +            parent="stock.menu_stock_product" sequence="3"/>
          <menuitem
              action="product.product_template_action" id="menu_product_template_config_stock"
              parent="stock.menu_product_in_config_stock" sequence="1"/>
              parent="stock.menu_product_in_config_stock"  sequence="35" groups="product.group_uom"/>
          <menuitem
              action="product.product_uom_categ_form_action" id="menu_stock_uom_categ_form_action"
 -            parent="menu_stock_configuration" sequence="30" groups="product.group_uom"/>
 +            parent="menu_product_in_config_stock" sequence="5" groups="product.group_uom"/>
          <menuitem
              action="product.product_uom_form_action" id="menu_stock_uom_form_action"
 -            parent="menu_stock_configuration" sequence="35" groups="product.group_uom"/>
 +            parent="menu_product_in_config_stock" sequence="4" groups="product.group_uom"/>
  
          <record id="stock_inventory_line_tree" model="ir.ui.view">
              <field name="name">stock.inventory.line.tree</field>
          </record>
          <menuitem action="action_production_lot_form" id="menu_action_production_lot_form"
              parent="menu_traceability" groups="stock.group_production_lot"
 -            sequence="1"/>
 +            sequence="2"/>
  
          #
          # Lot composition (history)
                              <field name="putaway_strategy_id"/>
                          </group>
                      </group>
 -                    <separator string="Additional Information"/>
 -                    <field name="comment"/>
 +                    <field name="comment" placeholder="External note..."/>
                  </form>
              </field>
          </record>
                  <tree string="Stock Location" colors="blue:usage=='view';darkred:usage=='internal'">
                      <field name="complete_name"/>
                      <field name="usage"/>
+                     <field name="company_id" groups="base.group_multi_company"/>
                  </tree>
              </field>
          </record>
              </field>
          </record>
          <menuitem action="action_location_form" id="menu_action_location_form" groups="stock.group_locations"
 -            parent="menu_stock_configuration" sequence="5"/>
 +            parent="menu_warehouse_config" sequence="2"/>
  
  
          <record id="act_product_stock_move_open" model="ir.actions.act_window">
              </field>
          </record>
          <menuitem action="action_warehouse_form" id="menu_action_warehouse_form" groups="stock.group_locations"
 -            parent="menu_stock_configuration" sequence="1"/>
 +            parent="menu_warehouse_config" sequence="1"/>
  
          <!--
              Stock picking
                          <group name="destination_grp" string="Destination" groups="stock.group_locations">
                              <field name="location_dest_id" />
                              <field name="partner_id" context="{'contact_display':'partner'}" />
 +                            <field name="move_dest_id" groups="base.group_no_one" readonly="1"/>
                          </group>
                          <group name="quants_grp" string="Reserved Quants" colspan="4" groups="base.group_no_one">
 -                            <field name="reserved_quant_ids"/>
 +                            <field name="reserved_quant_ids" nolabel="1"/>
                          </group>
                      </group>
                  </sheet>
                          <field name="create_date" invisible="1"/>
                          <field name="date"/>
                          <field name="date_expected" on_change="onchange_date(date,date_expected)"/>
 +                        <field name="move_dest_id" groups="base.group_no_one" readonly="1"/>
                      </group>
                      <group string="Locations" groups="stock.group_locations">
                          <field name="location_id" domain="[('usage','&lt;&gt;','view')]"/>
          <field name="act_window_id" ref="action_move_form2"/>
          </record>
  
 -        <menuitem action="action_move_form2" id="menu_action_move_form2" parent="menu_traceability" sequence="3" groups="stock.group_locations"/>
 +        <menuitem action="action_move_form2" id="menu_action_move_form2" parent="menu_traceability" sequence="1" groups="stock.group_locations"/>
  
          <!--
              Receipt Picking (By Stock Move)
              <field name="arch" type="xml">
                  <form string="Incoterms">
                      <sheet>
 -                        <field name="name"/>
 -                        <field name="code"/>
 -                        <field name="active"/>
 +                        <group>
 +                            <field name="name"/>
 +                            <field name="code"/>
 +                            <field name="active"/>
 +                        </group>
                      </sheet>
                  </form>
              </field>
              <field name="view_mode">tree,form</field>
          </record>
  
 -        <menuitem action="action_incoterms_tree" id="menu_action_incoterm_open" parent="menu_stock_configuration" sequence="40"/>
 -
 +        <menuitem action="action_incoterms_tree" id="menu_action_incoterm_open" parent="menu_warehouse_config" sequence="4"/>
  
           <record id="view_pickingtype_filter" model="ir.ui.view">
              <field name="name">stock.picking.type.filter</field>
                                          <t t-if="widget.view.is_action_enabled('edit')"><li><ul class="oe_kanban_colorpicker" data-field="color"/></li></t>
                                      </ul>
                                  </div>
 -                                <div class="oe_kanban_content">
 +                                <div class="oe_stock_kanban_content">
                                      <h4 class="text-center"><strong><field name="complete_name"/></strong></h4>
                                      <div class="oe_right">
                                          <a name="open_barcode_interface" type="object">
                                                  class="oe_stock_scan_image" title="Click to launch the barcode interface"/>
                                          </a>
                                      </div>
 -                                    <div class="oe_items_list oe_kanban_ellipsis">
 -                                        <div>
 -                                            <a name="%(action_picking_tree_ready)d" type="action">
 +                                    <div class="row">
 +                                        <div class="col-md-8">
 +                                            <a name="%(action_picking_tree_ready)d" type="action" class="col-md-9">
                                                  <field name="count_picking_ready"/> Ready
                                              </a>
 -                                            <a name="%(action_picking_tree_done)d" type="action" class="oe_sparkline_bar_link">
 +                                            <a name="%(action_picking_tree_done)d" type="action" class="col-md-3">
                                                  <field name="last_done_picking" widget="sparkline_bar" options="{'type': 'tristate', 'colorMap': {'0': 'orange', '-1': 'red', '1': 'green'}}">Last 10 Done Operations</field>
                                              </a>
 -                                        </div>
 -                                        <div t-if="record.count_picking_waiting.raw_value &gt; 0">
 -                                            <a name="%(action_picking_tree_waiting)d" type="action">
 -                                                <field name="count_picking_waiting"/> Waiting Availability
 -                                            </a>
 -                                        </div>
 -                                        <div>
 -                                            <a name="%(action_picking_tree)d" type="action">All Operations</a>
 +                                            <t t-if="record.count_picking_waiting.raw_value &gt; 0">
 +                                                 <a name="%(action_picking_tree_waiting)d" type="action" class="col-md-12">
 +                                                     <field name="count_picking_waiting"/> Waiting Availability
 +                                                 </a>
 +                                             </t>
 +                                             <a class="col-md-9" name="%(action_picking_tree)d" type="action">All Operations</a>
                                          </div>
                                      </div>
 -                                    <div class="oe_picking_type_gauge">
 -                                        <field name="rate_picking_late" widget="gauge" style="width:150px; height: 110px;" options="{'levelcolors': ['#a9d70b', '#f9c802', '#ff0000'], 'action_jump': '%(action_picking_tree_late)d'}">Late (%%)</field>
 -                                        <field name="rate_picking_backorders" widget="gauge" style="width:150px; height: 110px;">Backorders (%%)</field>
 -                                        <div class="oe_gauge_labels">
 -                                            <div class="oe_gauge_label_column">
 -                                                <a name="%(action_picking_tree_late)d" type="action">
 -                                                    <field name="count_picking_late"/> Late
 -                                                </a>
 -                                            </div>
 -                                            <div class="oe_gauge_label_column">
 -                                                <a name="%(action_picking_tree_backorder)d" type="action">
 -                                                    <field name="count_picking_backorders"/> Backorders
 -                                                </a>
 -                                            </div>
 -                                        </div>
 +                                    <div class="row">
 +                                         <field name="rate_picking_late" widget="gauge" style="margin-left:15px; width:150px; height: 110px;" options="{'levelcolors': ['#a9d70b', '#f9c802', '#ff0000'], 'action_jump': '%(action_picking_tree_late)d'}">Late (%%)</field>
 +                                         <field name="rate_picking_backorders" widget="gauge" style="margin-left:15px; width:150px; height: 110px;">Backorders (%%)</field>
 +                                         <a name="%(action_picking_tree_late)d" type="action" class="text-center col-md-6">
 +                                             <field name="count_picking_late"/> Late
 +                                         </a>
 +                                         <a name="%(action_picking_tree_backorder)d" type="action" class="text-center col-md-6">
 +                                             <field name="count_picking_backorders"/> Backorders
 +                                         </a>
                                      </div>
 -
                                  </div>
                              </div>
                          </t>
                      </group>
                  </xpath>
                  <xpath expr="//div[@name='button_box']" position="inside">
 -                    <button name="do_view_pickings" string="Group's Pickings" type="object"/>
 +                    <button name="do_view_pickings" class="oe_stat_button" icon="fa-bars" string="Group's Pickings" type="object"/>
                  </xpath>
                  <xpath expr="//field[@name='rule_id']" position="replace">
                      <field name="rule_id" domain="['|', ('location_id', '=', False), ('location_id', '=', location_id)]"/>
  
          <!-- Procurements are located in Warehouse menu hierarchy, MRP users should come to Stock application to use it.  -->
          <menuitem id="menu_stock_sched" name="Schedulers" parent="stock.menu_stock_root" sequence="4" groups="stock.group_stock_manager"/>
 -        <menuitem action="action_procurement_compute" id="menu_procurement_compute" parent="menu_stock_sched" groups="base.group_no_one"/>
 -        <menuitem action="procurement.action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="20" groups="stock.group_stock_manager"/>
 +        <menuitem action="action_procurement_compute" id="menu_procurement_compute" parent="menu_stock_sched" groups="base.group_no_one" sequence="2"/>
 +        <menuitem action="procurement.action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="1" groups="stock.group_stock_manager"/>
          <menuitem action="procurement.procurement_exceptions" id="menu_stock_procurement_action" parent="menu_stock_sched" sequence="50" groups="stock.group_stock_manager"/>
          <menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_configuration" sequence="5"/>
 -        <menuitem action="action_orderpoint_form" id="menu_stock_order_points" parent="stock.menu_stock_configuration" sequence="10"/>
 -        <menuitem id="stock.next_id_61" name="Warehouse" sequence="15" parent="base.menu_reporting" groups="group_stock_manager"/>
 +        <menuitem action="action_orderpoint_form" id="menu_stock_order_points" parent="stock.menu_schedulers_config" sequence="1"/>
 +        <menuitem id="stock.next_id_61" name="Warehouse" sequence="40" parent="base.menu_reporting" groups="group_stock_manager"/>
          <menuitem id="menu_quants" name="Quants" parent="menu_traceability" sequence="20" action="quantsact" groups="base.group_no_one"/>
 -        <menuitem id="menu_procurement_rules" name="Procurement Rules" parent="stock.menu_stock_configuration" action="procrules" groups="base.group_no_one"/>
 -        <menuitem id="menu_pickingtype" name="Types of Operation" parent="stock.menu_stock_configuration" action="action_picking_type_list"/>
 +        <menuitem id="menu_procurement_rules" name="Procurement Rules" parent="stock.menu_schedulers_config" action="procrules" groups="base.group_no_one" sequence="2"/>
 +        <menuitem id="menu_pickingtype" name="Operations Types" parent="stock.menu_warehouse_config" action="action_picking_type_list" sequence="3"/>
  
  
          <record model="ir.actions.act_window" id="product_open_orderpoint">
                  </p>
              </field>
          </record>
 -        <menuitem id="menu_package" name="Packages" parent="menu_stock_product" action="action_package_view" groups="stock.group_tracking_lot"/>
 +        <menuitem id="menu_package" name="Packages" parent="menu_stock_product" action="action_package_view" groups="stock.group_tracking_lot" sequence="4"/>
          
          
          <!--Routes-->
          </record>
          
          <menuitem action="action_routes_form" id="menu_stock_routes"
 -            parent="stock.menu_stock_configuration" sequence="11" />
 +            parent="stock.menu_schedulers_config" sequence="3" />
  
          <record id="do_view_pickings" model="ir.actions.act_window">
              <field name="name">Pickings for Groups</field>
@@@ -554,26 -554,6 +554,26 @@@ class WebClient(http.Controller)
      def jslist(self, mods=None):
          return manifest_list('js', mods=mods)
  
 +    @http.route('/web/webclient/locale/<string:lang>', type='http', auth="none")
 +    def load_locale(self, lang):
 +        magic_file_finding = [lang.replace("_",'-').lower(), lang.split('_')[0]]
 +        addons_path = http.addons_manifest['web']['addons_path']
 +        #load momentjs locale
 +        momentjs_locale_file = False
 +        momentjs_locale = ""
 +        for code in magic_file_finding:
 +            try:
 +                with open(os.path.join(addons_path, 'web', 'static', 'lib', 'moment', 'locale', code + '.js'), 'r') as f:
 +                    momentjs_locale = f.read()
 +                #we found a locale matching so we can exit
 +                break
 +            except IOError:
 +                continue
 +
 +        #return the content of the locale
 +        headers = [('Content-Type', 'application/javascript'), ('Cache-Control', 'max-age=%s' % (36000))]
 +        return request.make_response(momentjs_locale, headers)
 +
      @http.route('/web/webclient/qweb', type='http', auth="none")
      def qweb(self, mods=None, db=None):
          files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)]
@@@ -744,17 -724,14 +744,17 @@@ class Database(http.Controller)
              return {'error': _('Could not drop database !'), 'title': _('Drop Database')}
  
      @http.route('/web/database/backup', type='http', auth="none")
 -    def backup(self, backup_db, backup_pwd, token):
 +    def backup(self, backup_db, backup_pwd, token, **kwargs):
          try:
 +            format = kwargs.get('format')
 +            ext = "zip" if format == 'zip' else "dump"
              db_dump = base64.b64decode(
 -                request.session.proxy("db").dump(backup_pwd, backup_db))
 -            filename = "%(db)s_%(timestamp)s.dump" % {
 +                request.session.proxy("db").dump(backup_pwd, backup_db, format))
 +            filename = "%(db)s_%(timestamp)s.%(ext)s" % {
                  'db': backup_db,
                  'timestamp': datetime.datetime.utcnow().strftime(
 -                    "%Y-%m-%d_%H-%M-%SZ")
 +                    "%Y-%m-%d_%H-%M-%SZ"),
 +                'ext': ext
              }
              return request.make_response(db_dump,
                 [('Content-Type', 'application/octet-stream; charset=binary'),
@@@ -796,6 -773,7 +796,7 @@@ class Session(http.Controller)
              "user_context": request.session.get_context() if request.session.uid else {},
              "db": request.session.db,
              "username": request.session.login,
+             "company_id": request.env.user.company_id.id if request.session.uid else None,
          }
  
      @http.route('/web/session/get_session_info', type='json', auth="none")
@@@ -1016,6 -994,19 +1017,6 @@@ class View(http.Controller)
          }, request.context)
          return {'result': True}
  
 -    @http.route('/web/view/undo_custom', type='json', auth="user")
 -    def undo_custom(self, view_id, reset=False):
 -        CustomView = request.session.model('ir.ui.view.custom')
 -        vcustom = CustomView.search([('user_id', '=', request.session.uid), ('ref_id' ,'=', view_id)],
 -                                    0, False, False, request.context)
 -        if vcustom:
 -            if reset:
 -                CustomView.unlink(vcustom, request.context)
 -            else:
 -                CustomView.unlink([vcustom[0]], request.context)
 -            return {'result': True}
 -        return {'result': False}
 -
  class TreeView(View):
  
      @http.route('/web/treeview/action', type='json', auth="user")
@@@ -1561,6 -1552,7 +1562,6 @@@ class Reports(http.Controller)
      @serialize_exception
      def index(self, action, token):
          action = simplejson.loads(action)
 -
          report_srv = request.session.proxy("report")
          context = dict(request.context)
          context.update(action["context"])
              else:
                  file_name = action['report_name']
          file_name = '%s.%s' % (file_name, report_struct['format'])
 -
 +        headers=[
 +             ('Content-Disposition', content_disposition(file_name)),
 +             ('Content-Type', report_mimetype),
 +             ('Content-Length', len(report))]
 +        if action.get('pdf_viewer'):
 +            del headers[0]
          return request.make_response(report,
 -             headers=[
 -                 ('Content-Disposition', content_disposition(file_name)),
 -                 ('Content-Type', report_mimetype),
 -                 ('Content-Length', len(report))],
 +             headers=headers,
               cookies={'fileToken': token})
  
  class Apps(http.Controller):
     */
  }
  .openerp.openerp_webclient_container {
 +  height: 95%;
 +  height: -webkit-calc(100% - 34px);
    height: calc(100% - 34px);
 -  overflow: auto;
 +  display: -ms-flexbox;
 +  display: -webkit-flex;
 +  display: flex;
  }
  @media (max-width: 768px) {
    .openerp.openerp_webclient_container {
 +    height: 95%;
 +    height: -webkit-calc(100% - 52px);
      height: calc(100% - 52px);
    }
  }
    font-weight: bold;
    font-size: inherit;
  }
 -.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp .oe_button, .openerp input[type='submit'] {
 +.openerp a.button:link, .openerp a.button:visited, .openerp .oe_button, .openerp input[type='submit'] {
    display: inline-block;
    border: 1px solid rgba(0, 0, 0, 0.4);
    color: #4c4c4c;
    -webkit-font-smoothing: antialiased;
    outline: none;
  }
 -.openerp a.button:hover, .openerp button:hover, .openerp .oe_button:hover, .openerp input[type='submit']:hover {
 +.openerp a.button:hover, .openerp .oe_button:hover, .openerp input[type='submit']:hover {
    background-color: #ececec;
    background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3));
    background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3);
    cursor: pointer;
    background-position: 0;
  }
 -.openerp a.button:focus, .openerp button:focus, .openerp .oe_button:focus, .openerp input[type='submit']:focus {
 +.openerp a.button:focus, .openerp .oe_button:focus, .openerp input[type='submit']:focus {
    border: 1px solid #80bfff;
    background-position: 0;
    background-color: #ececec;
    -webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset;
    box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset;
  }
 -.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp .oe_button:active, .openerp .oe_button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active {
 +.openerp a.button:active, .openerp a.button.active, .openerp .oe_button:active, .openerp .oe_button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active {
    background-color: #ececec;
    background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6));
    background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6);
    -webkit-box-shadow: none;
    box-shadow: none;
  }
 -.openerp a.button.disabled, .openerp button:disabled, .openerp .oe_button:disabled, .openerp input[type='submit']:disabled {
 +.openerp a.button.disabled, .openerp .oe_button:disabled, .openerp input[type='submit']:disabled {
    background: #efefef !important;
    border: 1px solid #d1d1d1 !important;
    -moz-box-shadow: none !important;
  .openerp .oe_button.oe_link span:hover {
    text-decoration: underline;
  }
 -.openerp .oe_webclient .oe_star_on, .openerp .oe_webclient .oe_star_off {
 +.openerp .oe_star_on, .openerp .oe_star_off {
    color: #cccccc;
    text-shadow: 0 0 2px black;
    vertical-align: top;
    position: relative;
    top: -8px;
  }
 -.openerp .oe_webclient .oe_star_on:hover, .openerp .oe_webclient .oe_star_off:hover {
 +.openerp .oe_star_on:hover, .openerp .oe_star_off:hover {
    text-decoration: none;
  }
 -.openerp .oe_webclient .oe_star_on {
 +.openerp .oe_star_on {
    color: gold;
  }
  .openerp p.oe_grey {
    background-image: -webkit-radial-gradient(circle, #eee 0%, #ccc 40%, #bbb 100%);
    background-image: -moz-radial-gradient(#eee 0%, #ccc 40%, #bbb 100%);
    background-image: -ms-radial-gradient(#eee 0%, #ccc 40%, #bbb 100%);
 -  background-image: radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
 +  background-image: radial-gradient(circle, #eee 0%, #ccc 40%, #bbb 100%);
  }
  .openerp .oe_kanban_status_green {
    background: green;
    height: 16px;
  }
  .openerp .oe_form_field_radio.oe_vertical label {
 -  margin-left: 4px;
 +  display: inline-block;
 +  margin-left: 20px;
  }
 -.openerp .oe_form_field_radio.oe_form_required .oe_radio_input {
 +.openerp .oe_form_field_radio .oe_radio_input {
    border: 2px solid transparent;
    display: inline-block;
    height: 12px;
    vertical-align: top;
    border-radius: 10px;
    margin: 1px 0;
 +  position: absolute;
  }
  .openerp .oe_form_field_radio.oe_form_required.oe_form_invalid .oe_radio_input {
    border-color: red;
    filter: alpha(opacity=50);
    opacity: 0.5;
  }
 -.openerp .oe_sidebar {
 -  white-space: nowrap;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment {
 -  height: 20px;
 -  cursor: pointer;
 -  padding-left: 6px;
 -  margin-top: 6px;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment span {
 -  font-weight: bold;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment .oe_hidden_input_file {
 -  width: 200px;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment:hover {
 -  background-color: #efeff8;
 -  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6));
 -  background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6);
 -  background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6);
 -  background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6);
 -  background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6);
 -  background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6);
 -  -moz-box-shadow: none;
 -  -webkit-box-shadow: none;
 -  box-shadow: none;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu li .oe_sidebar_delete_item {
 -  position: absolute;
 -  top: 4px;
 -  right: 4px;
 -  display: none;
 -  width: 12px;
 -  height: 12px;
 -  padding: 1px;
 -  color: #8786b7;
 -  line-height: 8px;
 -  text-align: center;
 -  font-weight: bold;
 -  text-shadow: 0 1px 1px white;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu li .oe_sidebar_delete_item:hover {
 -  text-decoration: none;
 -  color: white;
 -  background: #8786b7;
 -  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
 -  -moz-border-radius: 2px;
 -  -webkit-border-radius: 2px;
 -  border-radius: 2px;
 -}
 -.openerp .oe_sidebar .oe_dropdown_menu li:hover .oe_sidebar_delete_item {
 -  display: inline-block;
 -}
  .openerp .oe_loading {
    display: none;
    z-index: 1001;
  .openerp .oe_database_manager .oe_database_manager_menu {
    color: #000;
  }
 -.openerp .oe_webclient {
 -  width: 100%;
 -  height: 100%;
 -  border-spacing: 0px;
 -}
  .openerp .oe_content_full_screen .oe_application {
    top: 0;
    left: 0;
    display: none;
  }
  .openerp .oe_leftbar {
 -  display: none;
 +  display: -ms-flexbox;
 +  display: -webkit-flex;
 +  display: flex;
 +  -ms-flex-direction: column;
 +  -webkit-flex-direction: column;
 +  flex-direction: column;
 +  -webkit-justify-content: space-between;
 +  justify-content: space-between;
 +  -ms-flex: 0 0 auto;
 +  -webkit-flex-shrink: 0;
 +  flex-shrink: 0;
 +  -webkit-flex-grow: 0;
 +  flex-grow: 0;
    width: 220px;
 +  height: 100%;
    background: #f0eeee;
    border-right: 1px solid #afafb6;
 -  text-shadow: none;
 -}
 -.openerp .oe_leftbar > div {
 -  height: 100%;
 -  display: table;
 -  width: 220px;
    position: relative;
 +  display: inline-block\9;
 +  vertical-align: top\9;
  }
 -.openerp .oe_leftbar > div a.oe_logo {
 -  display: table-row;
 +.openerp .oe_leftbar a.oe_logo {
    text-align: center;
 +  -webkit-flex-shrink: 0;
 +  flex-shrink: 0;
  }
 -.openerp .oe_leftbar > div a.oe_logo img {
 +.openerp .oe_leftbar a.oe_logo img {
    margin: 14px 0;
    border: 0;
  }
 -.openerp .oe_leftbar > div a.oe_logo .oe_logo_edit {
 +.openerp .oe_leftbar a.oe_logo .oe_logo_edit {
    margin: 14px 0;
    position: absolute;
    top: 1px;
    -ms-box-sizing: border-box;
    box-sizing: border-box;
  }
 -.openerp .oe_leftbar > div a.oe_logo:hover .oe_logo_edit_admin {
 +.openerp .oe_leftbar a.oe_logo:hover .oe_logo_edit_admin {
    display: block;
  }
 -.openerp .oe_leftbar > div > div {
 -  display: table-row;
 -  height: 100%;
 -}
 -.openerp .oe_leftbar > div > div > div {
 -  position: relative;
 -  height: 100%;
 -}
 -.openerp .oe_leftbar > div > div > div > div.oe_secondary_menus_container {
 -  position: absolute;
 -  position: static\9;
 -  top: 0;
 -  bottom: 0;
 -  left: 0;
 -  right: 0;
 -  overflow-x: hidden;
 +.openerp .oe_leftbar .oe_secondary_menus_container {
 +  -webkit-flex-grow: 1;
 +  flex-grow: 1;
    overflow-y: auto;
  }
 -.openerp .oe_leftbar > div .oe_footer {
 +.openerp .oe_leftbar .oe_footer {
    background: #f0eeee;
    text-align: center;
 +  -webkit-flex-shrink: 0;
 +  flex-shrink: 0;
  }
 -.openerp .oe_leftbar > div .oe_footer a {
 +.openerp .oe_leftbar .oe_footer a {
    font-weight: bold;
 -  color: black;
  }
 -.openerp .oe_leftbar > div .oe_footer a span {
 +.openerp .oe_leftbar .oe_footer a span {
    color: #a24689;
  }
  .openerp .oe_secondary_menu_section {
    text-decoration: underline;
  }
  .openerp .oe_application {
 -  width: 100%;
 -  height: 100%;
 -}
 -.openerp .oe_application a {
 -  color: #7C7BAD;
 -}
 -.openerp .oe_application > div {
 -  position: relative;
    height: 100%;
 +  -webkit-flex-grow: 1;
 +  flex-grow: 1;
 +  -ms-flex-negative: 1;
 +  display: inline-block\9;
 +  overflow: auto\9;
 +  width: -webkit-calc(100% - 220px);
 +  width: calc(100% - 220px);
 +}
 +.openerp .oe_application .oe_application {
 +  width: 100%;
  }
 -.openerp .oe_application > div > .oe_view_manager > .oe_view_manager_wrapper {
 -  display: table-row;
 +.openerp .oe-view-manager {
 +  width: 100%;
    height: 100%;
 +  display: -webkit-flex;
 +  display: flex;
 +  -webkit-flex-direction: column;
 +  flex-direction: column;
  }
 -.openerp .oe_application > div > .oe_view_manager > .oe_view_manager_wrapper > div {
 +.openerp .oe-view-manager .oe-view-manager-content {
 +  overflow: auto;
 +  -webkit-flex-grow: 1;
 +  flex-grow: 1;
    position: relative;
 -  height: 100%;
  }
 -.openerp .oe_application > div > .oe_view_manager > .oe_view_manager_wrapper > div > .oe_view_manager_body {
 +.openerp .oe-view-manager .oe-view-manager-content a {
 +  color: #7C7BAD;
 +}
 +.openerp .oe-view-manager .oe-view-manager-content > div {
    position: absolute;
    position: static\9;
    top: 0;
    bottom: 0;
 -  left: 0;
    right: 0;
 -  overflow: auto;
 -}
 -.openerp .oe_application .oe_breadcrumb_item:not(:last-child) {
 -  max-width: 7em;
 -  white-space: nowrap;
 -  text-overflow: ellipsis;
 -}
 -.openerp .oe_application .oe_breadcrumb_title > * {
 -  display: inline-block;
 -  overflow: hidden;
 -  font-weight: bold;
 -}
 -.openerp .oe_view_manager {
 -  display: table;
 -  height: inherit;
 -  width: 100%;
 +  left: 0;
 +  display: none;
  }
 -.openerp .oe_view_manager .oe_view_manager_view_kanban:not(:empty) {
 -  height: 100%;
 +.openerp .oe-view-manager .oe-view-manager-content .oe-view-manager-content > div {
 +  position: relative;
 +  display: block;
  }
 -.openerp .oe_view_manager[data-view-type=kanban] .oe_view_manager_body {
 -  display: table-row;
 +.openerp .oe-view-manager .oe-view-manager-debug {
 +  margin-right: 5px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header {
 -  border-collapse: separate;
 +.openerp .oe-view-manager-header {
 +  background-color: #f0eeee;
 +  border-bottom: 1px solid #afafb6;
 +  -webkit-flex-shrink: 0;
 +  flex-shrink: 0;
    width: 100%;
 -  table-layout: fixed;
 +  -webkit-user-select: none;
 +  -moz-user-select: none;
 +  user-select: none;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row {
 -  clear: both;
 -  text-shadow: 0 1px 1px white;
 -}
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row:last-child td {
 -  padding-top: 0;
 -}
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row:first-child td {
 -  padding-top: 8px;
 +.openerp .oe-view-manager-header .dropdown-menu li {
 +  position: relative;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_sidebar {
 -  margin: 0px auto;
 -  text-align: center;
 +.openerp .oe-view-manager-header .dropdown-menu li a {
 +  padding: 3px 25px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_sidebar .oe_dropdown_arrow:after {
 -  opacity: 0.9;
 +.openerp .oe-view-manager-header .dropdown-menu .oe_searchview_custom_public a:after {
 +  font-family: FontAwesome;
 +  content: "";
 +  color: #666;
 +  margin-left: 3px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header td {
 -  line-height: 26px;
 +.openerp .oe-view-manager-header .selected {
 +  display: block;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header h2 {
 -  font-size: 18px;
 -  margin: 0;
 -  float: left;
 -  line-height: 30px;
 +.openerp .oe-view-manager-header .selected a {
 +  font-weight: bold;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header h2 a {
 -  color: #7C7BAD;
 +.openerp .oe-view-manager-header .selected a:before {
 +  font-family: FontAwesome;
 +  position: absolute;
 +  left: 6px;
 +  top: 3px;
 +  content: "";
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_dropdown_menu {
 -  line-height: normal;
 +.openerp .oe-view-manager-header .oe-right-toolbar {
 +  float: right;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group {
 +.openerp .oe-view-manager-header .oe-right-toolbar > div {
    display: inline-block;
 -  border: 1px solid #ababab;
 -  -moz-border-radius: 5px;
 -  -webkit-border-radius: 5px;
 -  border-radius: 5px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group li {
 -  float: left;
 -  border-right: 1px solid #ababab;
 +.openerp .oe-view-manager-header .row:first-child {
 +  padding-top: 3px;
 +  padding-bottom: 3px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group li:last-child {
 -  border: none;
 +.openerp .oe-view-manager-header .row:last-child {
 +  padding-bottom: 10px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group a {
 -  color: #4c4c4c;
 +.openerp .oe-view-manager-header .oe_tag {
 +  -moz-border-radius: 0px;
 +  -webkit-border-radius: 0px;
 +  border-radius: 0px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group a:hover {
 -  text-decoration: none;
 +.openerp .oe-view-manager-header .oe-view-title {
 +  font-size: 18px;
 +  padding-left: 0;
 +  margin: 0;
 +  background-color: #f0eeee;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group .active {
 -  background: #999;
 -  -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset;
 -  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset;
 -  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset;
 +.openerp .oe-view-manager-header .oe-view-title li {
 +  -moz-user-select: initial;
 +  -webkit-user-select: initial;
 +  user-select: initial;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group .active a {
 -  color: #fff;
 -  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
 +.openerp .oe-view-manager-header .oe-view-manager-search-view {
 +  padding-top: 5px;
  }
 -.openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_buttons {
 -  white-space: nowrap;
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-kanban:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch {
 -  padding: 0;
 -  margin: 0 0 0 8px;
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-list:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch li {
 -  margin: 0;
 -  width: 24px;
 -  height: 24px;
 -  line-height: 16px;
 -  padding: 0;
 -  text-align: center;
 -  list-style-type: none;
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-form:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch li a {
 -  position: relative;
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-graph:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_list:after, .openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_tree:after {
 -  padding: 2px;
 -  content: "i";
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-calendar:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_form:after {
 -  content: "m";
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-gantt:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_graph:after {
 -  font-family: "mnmliconsRegular" !important;
 -  font-size: 21px;
 -  font-weight: 300 !important;
 -  content: "}";
 -  top: -2px;
 -  position: relative;
 +.openerp .oe-view-manager-header .oe-view-manager-switch .oe-vm-switch-pivot:before {
 +  content: "";
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_gantt:after {
 -  font-family: "mnmliconsRegular" !important;
 -  font-size: 21px;
 -  font-weight: 300 !important;
 -  content: "y";
 -  top: -2px;
 -  position: relative;
 +.openerp .oe-view-manager-header .oe-view-manager-buttons {
 +  display: inline-block;
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_calendar:after {
 -  content: "P";
 +.openerp .oe-view-manager-header .oe-view-manager-buttons > div {
 +  display: none;
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_kanban:after {
 -  content: "k";
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar {
 +  display: inline-block;
 +  float: right;
  }
 -.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_diagram:after {
 -  content: "f";
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_form_binary_form {
 +  cursor: pointer;
  }
 -.openerp .oe_list_pager {
 -  line-height: 26px;
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_form_binary_form span {
 +  padding: 3px 20px;
  }
 -.openerp .oe_pager_value {
 -  float: left;
 -  margin-right: 8px;
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_form_binary_form input.oe_form_binary_file {
 +  width: 100%;
  }
 -.openerp ul.oe_pager_group {
 -  padding: 0;
 -  margin: 0;
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_form_binary_form:hover {
 +  background-color: #f5f5f5;
  }
 -.openerp .oe_pager_group {
 -  float: left;
 -  height: 24px;
 -  line-height: 24px;
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_file_attachment {
 +  padding: 3px 20px;
    display: inline-block;
 -  border: 1px solid #ababab;
 -  cursor: pointer;
 -  -moz-border-radius: 5px;
 -  -webkit-border-radius: 5px;
 -  border-radius: 5px;
  }
 -.openerp .oe_pager_group li {
 -  height: 24px;
 -  line-height: 24px;
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_sidebar_delete_item {
    padding: 0;
 -  margin: 0;
 -  list-style-type: none;
 -  float: left;
 -  border-right: 1px solid #ababab;
 +  display: inline-block;
  }
 -.openerp .oe_pager_group li:last-child {
 -  border: none;
 +.openerp .oe-view-manager-header .oe-view-manager-sidebar .dropdown-menu li a {
 +  width: 100%;
  }
 -.openerp .oe_pager_group a {
 -  color: #4c4c4c;
 -  padding: 0 8px;
 +.openerp .oe-view-manager-header .oe_form_buttons {
 +  padding: 0;
  }
 -.openerp .oe_pager_group a:hover {
 -  text-decoration: none;
 +.openerp .oe-view-manager-header .oe_form_buttons_view > button {
 +  float: left;
  }
 -.openerp .oe_pager_group .active {
 -  background: #999;
 -  -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset;
 -  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset;
 -  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset;
 +.openerp .oe-view-manager-header .oe_form_buttons_view > button:last-child {
 +  float: right;
 +  margin-left: 4px;
  }
 -.openerp .oe_pager_group .active a {
 -  color: #fff;
 -  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
 +.openerp .oe-view-manager-header .oe-pager-buttons {
 +  min-height: 30px;
  }
 -.openerp .oe_list_pager.oe_list_pager_single_page .oe_pager_group {
 +.openerp .oe_view_manager_inline > .oe-view-manager-header, .openerp .oe_view_manager_inlineview > .oe-view-manager-header {
    display: none;
  }
 -.openerp .oe_view_manager_current {
 -  height: 100%;
 -}
 -.openerp .oe_view_manager_current > .oe_view_manager_header {
 -  border-bottom: 1px solid #cacaca;
 -  background-color: #ededed;
 -  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede));
 -  background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede);
 -  background-image: -moz-linear-gradient(top, #fcfcfc, #dedede);
 -  background-image: -ms-linear-gradient(top, #fcfcfc, #dedede);
 -  background-image: -o-linear-gradient(top, #fcfcfc, #dedede);
 -  background-image: linear-gradient(to bottom, #fcfcfc, #dedede);
 -  -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1);
 -  -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1);
 -  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1);
 -}
 -.openerp .oe_view_manager_current > .oe_view_manager_header .oe_header_row td {
 -  padding: 8px;
 -}
 -.openerp .oe_view_manager_current > .oe_view_manager_header .oe_header_row:first-child td {
 -  padding-top: 8px;
 -}
 -.openerp .oe_view_manager_inline, .openerp .oe_view_manager_inlineview {
 -  height: 100%;
 -}
 -.openerp .oe_view_manager_inline > .oe_view_manager_header, .openerp .oe_view_manager_inlineview > .oe_view_manager_header {
 -  display: none;
 +.openerp .o-modal-header > div {
 +  margin-left: 45%;
  }
  .openerp .oe_popup_form {
 -  display: table;
 +  display: none;
  }
  .openerp .oe_popup_form .oe_formview .oe_form_pager {
    display: none !important;
  .openerp .oe_searchview {
    cursor: text;
    position: relative;
 -  float: right;
 -  padding: 1px 0;
 -  line-height: 18px;
 -  min-width: 400px;
 -  border: 1px solid #ababab;
 -  background: white;
 -  -moz-border-radius: 13px;
 -  -webkit-border-radius: 13px;
 -  border-radius: 13px;
 -  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset;
 -  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset;
 -  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset;
 -}
 -.openerp .oe_searchview input, .openerp .oe_searchview textarea {
 -  padding: 3px;
 -  height: 14px;
 -  font-size: 12px;
 -  line-height: 18px;
 -}
 -.openerp .oe_searchview input:not([type]), .openerp .oe_searchview input[type="text"], .openerp .oe_searchview input[type="number"] {
 -  width: 156px;
 -  height: 22px;
 -}
 -.openerp .oe_searchview input[type="checkbox"] {
 -  margin: 3px 3px 3px 4px;
 -}
 -.openerp .oe_searchview select {
 -  margin: 2px 4px 2px 0;
 -}
 -.openerp .oe_searchview.oe_focused {
 -  border-color: #a6a6fe;
 -  -moz-box-shadow: 0 1px 2px #a6a6fe inset;
 -  -webkit-box-shadow: 0 1px 2px #a6a6fe inset;
 -  box-shadow: 0 1px 2px #a6a6fe inset;
 -}
 -.openerp .oe_searchview .oe_searchview_clear {
 -  cursor: pointer;
 -  position: absolute;
 -  top: 0;
 -  right: 18px;
 -  width: 15px;
 -  height: 24px;
 -  background: url(../img/search_reset.gif) center center no-repeat;
 +  display: none;
 +  height: auto;
 +  padding-top: 1px;
 +  padding-bottom: 1px;
  }
  .openerp .oe_searchview .oe_searchview_unfold_drawer {
    position: absolute;
 -  top: 0;
 -  right: 0;
 -  height: 24px;
 -  padding: 0 7px 0 4px;
 -  color: #ccc;
 +  top: 8px;
 +  right: 5px;
    cursor: pointer;
  }
 -.openerp .oe_searchview .oe_searchview_unfold_drawer:hover {
 -  color: #999;
 -}
 -.openerp .oe_searchview .oe_searchview_unfold_drawer:before {
 -  position: absolute;
 -  top: 10px;
 -  right: 7px;
 -  width: 0;
 -  height: 0;
 -  display: inline-block;
 -  content: "";
 -  vertical-align: top;
 -  border-top: 5px solid #4C4C4C;
 -  border-left: 5px solid transparent;
 -  border-right: 5px solid transparent;
 -  filter: alpha(opacity=50);
 -  opacity: 0.5;
 -}
  .openerp .oe_searchview .oe_searchview_search {
 -  font-size: 1px;
 -  letter-spacing: -1px;
 -  color: transparent;
 -  text-shadow: none;
 -  font-weight: normal;
 -  -moz-box-shadow: none;
 -  -webkit-box-shadow: none;
 -  box-shadow: none;
 -  -moz-border-radius: 0;
 -  -webkit-border-radius: 0;
 -  border-radius: 0;
 +  cursor: pointer;
    position: absolute;
 -  left: 3px;
 -  top: 1px;
 -  padding: 0;
 -  border: none;
 -  background: transparent;
 -}
 -.openerp .oe_searchview .oe_searchview_search:before {
 -  font: 21px "mnmliconsRegular";
 -  content: "r";
 -  color: #a3a3a3;
 +  top: 8px;
 +  left: 5px;
  }
  .openerp .oe_searchview .oe_searchview_facets {
    min-height: 22px;
 -  margin: 0 35px 0 15px;
 +  margin: 2px 20px 0 0;
  }
  .openerp .oe_searchview .oe_searchview_facets * {
    vertical-align: top;
    display: inline-block;
 -  line-height: 17px;
 +  font-size: 100%;
  }
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet {
    margin: 1px 0;
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus {
    outline: none;
  }
 +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .label {
 +  -moz-border-radius: 0px;
 +  -webkit-border-radius: 0px;
 +  border-radius: 0px;
 +}
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_input {
 -  padding: 0 0 0 6px;
 +  padding: 2px 0 0 6px;
    font-size: 12px;
    height: 16px;
 -  margin-top: 3px;
 +  -webkit-user-select: initial;
 +  -moz-user-select: initial;
 +  user-select: initial;
  }
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus {
    outline: none;
  }
 +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:first-child {
 +  margin-left: 10px;
 +}
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet {
    position: relative;
    cursor: pointer;
  }
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_values {
    background: #f0f0fa;
 -  -moz-border-radius: 0 3px 3px 0;
 -  -webkit-border-radius: 0 3px 3px 0;
 -  border-radius: 0 3px 3px 0;
  }
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_category, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value {
    padding: 0 4px;
  }
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value {
    border-left: 1px solid #afafb6;
 -  text-shadow: 0 1px 1px white;
    color: #4C4C4C;
  }
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:last-child {
    padding-right: 16px;
  }
 +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:first-child {
 +  border-left: none;
 +}
  .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_remove {
    position: absolute;
    top: 3px;
  .openerp .oe_searchview .oe-autocomplete {
    display: none;
    position: absolute;
 -  width: 300px;
    background-color: white;
 +  width: 400px;
    border: 1px solid #afafb6;
    z-index: 666;
    margin-top: 2px;
    -moz-border-radius: 3px;
    -webkit-border-radius: 3px;
    border-radius: 3px;
 -  -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
 -  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
 -  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  }
  .openerp .oe_searchview .oe-autocomplete ul {
    list-style-type: none;
  .openerp .oe_searchview .oe-autocomplete ul li.oe-separator:last-child {
    display: none;
  }
 -.openerp .oe_searchview_drawer_container {
 -  overflow: auto;
 -}
 -.openerp .oe_searchview_drawer {
 -  display: none;
 +.openerp .oe-search-options a {
 +  padding-left: 25px;
 +  padding-right: 25px;
    width: 100%;
 -  cursor: default;
 -  display: none;
 -  overflow: hidden;
 -  border-bottom: 1px solid #afafb6;
 -  text-align: left;
 -  padding: 8px 0;
 +  -webkit-user-select: none;
 +  -moz-user-select: none;
 +  user-select: none;
  }
 -.openerp .oe_searchview_drawer .badge {
 -  font-size: 12px;
 -  line-height: 12px;
 +.openerp .oe-search-options .oe-apply-filter {
 +  margin-left: 25px;
  }
 -.openerp .oe_searchview_drawer > div:first-child {
 -  border: none;
 -  padding-left: 0;
 -}
 -.openerp .oe_searchview_drawer > div:first-child li:hover:not(.badge) {
 -  background-color: #f0f0fa;
 +.openerp .oe-search-options .oe-add-filter-menu {
 +  display: none;
  }
 -.openerp .oe_searchview_drawer .col-md-5 {
 +.openerp .oe-search-options .oe-add-condition {
 +  width: auto;
 +  margin-left: 10px;
    padding-left: 0;
 +  padding-right: 10px;
  }
 -.openerp .oe_searchview_drawer dl {
 -  margin-bottom: 0;
 -}
 -.openerp .oe_searchview_drawer dt {
 -  color: #7C7BAD;
 -  font-size: 13px;
 -  line-height: 24px;
 -}
 -.openerp .oe_searchview_drawer dd {
 -  line-height: 24px;
 -  font-size: 13px;
 -  padding-top: 3px;
 -}
 -.openerp .oe_searchview_drawer h4, .openerp .oe_searchview_drawer h4 * {
 -  margin: 0 0 0 2px;
 -  padding-left: 20px;
 -  cursor: pointer;
 -  font-weight: normal;
 -  display: inline-block;
 -}
 -.openerp .oe_searchview_drawer h4:hover, .openerp .oe_searchview_drawer h4 *:hover {
 -  background-color: #f0f0fa;
 -}
 -.openerp .oe_searchview_drawer h4:before {
 -  content: "▸ ";
 -  color: #a3a3a3;
 -}
 -.openerp .oe_searchview_drawer button {
 -  margin: 4px 0;
 -}
 -.openerp .oe_searchview_drawer .button {
 -  border: none;
 -  background: transparent;
 -  padding: 0 2px;
 -  -moz-box-shadow: none;
 -  -webkit-box-shadow: none;
 -  box-shadow: none;
 -  -moz-border-radius: 0;
 -  -webkit-border-radius: 0;
 -  border-radius: 0;
 +.openerp .oe-search-options .oe-group-selector {
 +  margin: 3px 25px;
 +  min-width: -webkit-calc(100% - 40px);
 +  min-width: calc(100% - 40px);
 +  max-width: 250px;
 +  width: auto;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_section ul {
 -  margin: 0 8px;
 -  padding: 0;
 -  list-style: none;
 -  display: inline;
 +.openerp .oe-search-options .divider {
 +  display: none;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_section li {
 -  display: inline-block;
 -  cursor: pointer;
 -  position: relative;
 -  margin-right: 8px;
 +.openerp .oe-search-options .filters-menu {
 +  overflow: auto;
 +  overflow-x: hidden;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_section li > span {
 -  display: inline-block;
 -  max-width: 250px;
 -  text-overflow: ellipsis;
 -  vertical-align: bottom;
 -  overflow: hidden;
 +.openerp .oe-search-options .filters-menu .divider {
 +  display: list-item;
  }
 -.openerp .oe_searchview_drawer form {
 -  margin-left: 12px;
 +.openerp .oe-search-options .closed-menu a:before {
 +  font-family: FontAwesome;
 +  position: absolute;
 +  left: 12px;
 +  top: 3px;
 +  content: "";
  }
 -.openerp .oe_searchview_drawer form p {
 -  margin: 4px 0;
 -  line-height: 18px;
 +.openerp .oe-search-options .open-menu a:before {
 +  font-family: FontAwesome;
 +  position: absolute;
 +  left: 9px;
 +  top: 3px;
 +  content: "";
  }
 -.openerp .oe_searchview_drawer form button {
 -  margin: 0 0 8px -3px;
 +.openerp .oe-search-options .oe-select-group {
 +  margin: 3px 25px;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_savefilter form {
 +.openerp .oe-search-options .oe-add-group {
    display: none;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom {
 +.openerp .oe-search-options .oe-save-name {
    display: none;
 +  margin: 3px 25px;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom li {
 -  cursor: pointer;
 -  position: relative;
 -  line-height: 14px;
 -  margin-right: 0;
 +.openerp .oe-search-options .oe-save-name span {
 +  white-space: nowrap;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom li button {
 -  position: absolute;
 -  top: 0;
 -  right: 5px;
 +.openerp .oe-search-options .oe-save-name span input {
 +  margin-left: 0;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom li a {
 -  margin-left: 10px;
 -  position: inherit;
 -  visibility: hidden;
 -  display: inline-block;
 +.openerp .oe-search-options .searchview_extended_prop_field {
 +  display: inline;
 +  width: -webkic-calc(100% - 20px);
 +  width: calc(100% - 20px);
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom li span:hover:not(.badge) {
 -  background-color: #f0f0fa;
 +.openerp .oe-search-options .searchview_extended_prop_op {
 +  margin: 3px 0;
 +  width: -webkit-calc(100% - 20px);
 +  width: calc(100% - 20px);
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom li:hover a {
 -  visibility: visible;
 +.openerp .oe-search-options .searchview_extended_delete_prop {
 +  float: right;
 +  display: inline;
 +  cursor: pointer;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom label {
 -  font-weight: normal;
 +.openerp .oe-search-options .searchview_extended_prop_value {
 +  width: -webkit-calc(100% - 20px);
 +  width: calc(100% - 20px);
  }
 -.openerp .oe_searchview_drawer .oe_searchview_dashboard form {
 -  display: none;
 -  margin-top: 2px;
 +.openerp .oe-search-options .searchview_extended_prop_value > select, .openerp .oe-search-options .searchview_extended_prop_value > input {
 +  width: -webkit-calc(100% - 20px);
 +  width: calc(100% - 20px);
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced {
 -  overflow: auto;
 +.openerp .oe-search-options .searchview_extended_prop_value .oe_datepicker_main {
 +  width: -webkit-calc(100% - 20px);
 +  width: calc(100% - 20px);
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced form {
 -  display: none;
 -  margin-top: 8px;
 +.openerp .oe-search-options .searchview_extended_prop_value .oe_datepicker_master {
 +  width: 100%;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced button.oe_add_condition:before {
 -  content: "Z";
 -  font-family: "entypoRegular" !important;
 -  font-size: 24px;
 -  font-weight: 300 !important;
 -  margin-right: 4px;
 +.openerp .oe-search-options .searchview_extended_prop_value .fa-calendar {
 +  margin-left: -21px;
 +  cursor: pointer;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced ul {
 -  list-style: none;
 -  padding: 0;
 +.openerp .oe-search-options span.remove-filter {
 +  position: absolute;
 +  top: 6px;
 +  right: 10px;
 +  cursor: pointer;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced li {
 +.openerp .oe-search-options .dropdown-menu > li {
    position: relative;
 -  list-style: none;
 -  margin: 0;
    white-space: nowrap;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced li:first-child .searchview_extended_prop_or {
 -  visibility: hidden;
 -  margin-left: -14px;
 +.openerp .oe-search-options .dropdown-menu li.oe-filter-condition {
 +  white-space: normal;
 +  padding-left: 25px;
 +  padding-right: 10px;
 +  margin-bottom: 5px;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_advanced .searchview_extended_prop_or {
 -  opacity: 0.5;
 -  margin-left: -14px;
 +.openerp .oe-search-options .dropdown-menu li.oe-filter-condition .o-or-filter {
 +  display: none;
  }
 -.openerp .oe_searchview_drawer .oe_opened h4:before {
 -  content: "▾ ";
 -  position: relative;
 -  top: -1px;
 +.openerp .oe-search-options .dropdown-menu li.oe-filter-condition + li.oe-filter-condition {
 +  margin-top: 10px;
  }
 -.openerp .oe_searchview_drawer .oe_opened form {
 +.openerp .oe-search-options .dropdown-menu li.oe-filter-condition + li.oe-filter-condition .o-or-filter {
    display: block;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom_delete, .openerp .oe_searchview_drawer .searchview_extended_delete_prop {
 -  display: inline-block;
 -  width: 12px;
 -  height: 12px;
 -  line-height: 12px;
 -  padding: 1px;
 -  color: #8786b7;
 -  line-height: 8px;
 -  text-align: center;
 -  font-weight: bold;
 -  text-shadow: 0 1px 1px white;
 -}
 -.openerp .oe_searchview_drawer .oe_searchview_custom_delete:hover, .openerp .oe_searchview_drawer .searchview_extended_delete_prop:hover {
 -  text-decoration: none;
 -  color: white;
 -  background: #8786b7;
 -  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
 -  -moz-border-radius: 2px;
 -  -webkit-border-radius: 2px;
 -  border-radius: 2px;
 -}
 -.openerp .oe_searchview_drawer .oe_searchview_custom_delete {
 -  display: none;
 +.openerp .oe-search-options .dropdown-menu .o-or-filter {
    position: absolute;
 -  bottom: 1px;
 -  right: 4px;
 +  left: 5px;
 +  top: 5px;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom_private:hover .oe_searchview_custom_delete, .openerp .oe_searchview_drawer .oe_searchview_custom_public:hover .oe_searchview_custom_delete {
 +.openerp .oe-search-options a {
    display: inline-block;
  }
 -.openerp .oe_searchview_drawer .oe_searchview_custom_public:after {
 -  content: ",";
 -  font-family: "entypoRegular" !important;
 -  font-size: 22px;
 -  font-weight: 300 !important;
 -  margin: 0 0 0 4px;
 -  padding: 0;
 +.openerp input.oe-save-name {
 +  width: auto;
 +}
 +.openerp .oe_pager_value {
 +  display: inline-block;
  }
  .openerp .oe_view_nocontent {
    padding: 15px;
    border-right: none;
  }
  .openerp .oe_form .oe_subtotal_footer .oe_subtotal_footer_separator {
-   width: 108px;
+   min-width: 108px;
    border-top: 1px solid #cacaca;
    margin-top: 4px;
    padding-top: 4px;
  .openerp .oe_form .oe_form_field_boolean {
    width: auto;
  }
  .openerp .oe_form .oe_datepicker_root {
    display: inline-block;
  }
 +.openerp .oe_form .oe_datepicker_root .datepickerbutton {
 +  margin-left: 5px;
 +  cursor: pointer;
 +}
  .openerp .oe_form .oe_form_required input:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required select:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required textarea:not([disabled]):not([readonly]) {
    background-color: #D2D2FF !important;
  }
  .openerp .oe_form_field_one2many > .oe_view_manager .oe_view_manager_view_list, .openerp .oe_form_field_many2many > .oe_view_manager .oe_view_manager_view_list {
    min-height: 132px;
  }
 -.openerp .oe_form_field_one2many .oe_form_field_one2many_list_row_add, .openerp .oe_form_field_many2many .oe_form_field_one2many_list_row_add {
 +.openerp .oe_form_field_one2many .oe_form_field_one2many_list_row_add, .openerp .oe_form_field_one2many .oe_form_field_many2many_list_row_add, .openerp .oe_form_field_many2many .oe_form_field_one2many_list_row_add, .openerp .oe_form_field_many2many .oe_form_field_many2many_list_row_add {
    font-weight: bold;
  }
  .openerp .oe_form_field_one2many .oe_list_content > thead, .openerp .oe_form_field_many2many .oe_list_content > thead {
  }
  .openerp .oe_debug_view {
    float: left;
 +  margin-top: 5px;
 +  width: auto;
  }
  .openerp .oe_debug_view_log {
    font-size: 95%;
    font-size: 11px;
    background-color: #7c7bad;
  }
 -.openerp button, .openerp body {
 -  line-height: normal;
 -}
  .openerp h1, .openerp h2 {
    font-weight: bold;
  }
  }
  
  @-moz-document url-prefix() {
 -  .openerp .oe_searchview .oe_searchview_search {
 -    top: -1px;
 -  }
    .openerp .oe_form_field_many2one .oe_m2o_cm_button {
      line-height: 18px;
    }
@@@ -3096,21 -3357,7 +3096,21 @@@ body.oe_single_form .oe_single_form_con
    font-size: 20px;
  }
  .modal .modal-body {
 -  overflow-x: auto;
 +  overflow: auto;
 +  padding: 0;
 +}
 +.modal .modal-body .oe_application {
 +  width: 100%;
 +}
 +.modal .modal-body .oe_popup_list {
 +  display: none;
 +  min-height: 150px;
 +}
 +.modal .modal-body .oe-view-manager {
 +  min-height: 150px;
 +}
 +.modal .modal-body .oe_form_sheetbg .oe_form_sheet {
 +  padding: 0;
  }
  .modal .modal-footer {
    text-align: left;
  .modal .oe_act_window.modal-body {
    padding: 0;
  }
 +.modal .oe-view-manager-content > div {
 +  position: static !important;
 +}
  
 +@media (min-width: 768px) {
 +  .modal .modal-body {
 +    max-height: -webkit-calc(100vh - 170px);
 +    max-height: calc(100vh - 170px);
 +  }
 +}
  .ui-datepicker {
    z-index: 1500 !important;
  }
@@@ -3183,10 -3421,7 +3183,10 @@@ body 
  }
  
  @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
 -  .oe_secondary_menus_container {
 -    position: static !important;
 +  .oe-view-manager-content > div {
 +    position: absolute !important;
 +  }
 +  .oe-view-manager-content .oe-view-manager-content > div {
 +    position: relative !important;
    }
  }
@@@ -198,16 -198,10 +198,16 @@@ $sheet-padding: 16p
      font-size: 13px
      background: white
      &.openerp_webclient_container
 +        height: 95%
 +        height: -webkit-calc(100% - 34px)
          height: calc(100% - 34px)
 -        overflow: auto
 +        display: -ms-flexbox
 +        display: -webkit-flex
 +        display: flex
      @media (max-width: 768px)
          &.openerp_webclient_container
 +            height: 95%
 +            height: -webkit-calc(100% - 52px)
              height: calc(100% - 52px)
      // }}}
      //Placeholder style{{{
          font-size: inherit
      // }}}
      // Button style {{{
 -    a.button:link, a.button:visited, button, .oe_button, input[type='submit']
 +    a.button:link, a.button:visited, .oe_button, input[type='submit']
          display: inline-block
          border: 1px solid rgba(0,0,0,0.4)
          color: #4c4c4c
          -webkit-font-smoothing: antialiased
          outline: none
  
 -    a.button:hover, button:hover,.oe_button:hover, input[type='submit']:hover
 +    a.button:hover,.oe_button:hover, input[type='submit']:hover
          @include vertical-gradient(#f6f6f6, #e3e3e3)
          cursor: pointer
          background-position: 0
  
 -    a.button:focus, button:focus, .oe_button:focus, input[type='submit']:focus
 +    a.button:focus, .oe_button:focus, input[type='submit']:focus
          border: 1px solid #80bfff
          background-position: 0
          @include vertical-gradient(#f6f6f6, #e3e3e3)
          @include box-shadow((0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, .8) inset))
  
 -    a.button:active, a.button.active, button:active, .oe_button:active, .oe_button.active, input[type='submit']:active, input[type='submit'].active
 +    a.button:active, a.button.active, .oe_button:active, .oe_button.active, input[type='submit']:active, input[type='submit'].active
          @include vertical-gradient(#e3e3e3, #f6f6f6)
          @include box-shadow(none)
  
 -    a.button.disabled, button:disabled, .oe_button:disabled, input[type='submit']:disabled
 +    a.button.disabled, .oe_button:disabled, input[type='submit']:disabled
          background: #efefef !important
          border: 1px solid #d1d1d1 !important
          @include box-shadow(none !important)
              font-weight: bold
              &:hover
                  text-decoration: underline
 -    .oe_webclient
 -        .oe_star_on, .oe_star_off
 -            color: #cccccc
 -            text-shadow: 0 0 2px black
 -            vertical-align: top
 -            position: relative
 -            top: -8px
 -        .oe_star_on:hover, .oe_star_off:hover
 -            text-decoration: none
 -        .oe_star_on
 -            color: gold
 +    .oe_star_on, .oe_star_off
 +        color: #cccccc
 +        text-shadow: 0 0 2px black
 +        vertical-align: top
 +        position: relative
 +        top: -8px
 +    .oe_star_on:hover, .oe_star_off:hover
 +        text-decoration: none
 +    .oe_star_on
 +        color: gold
      p.oe_grey
          max-width: 650px
      .oe_grey
                  height: 16px
          &.oe_vertical
              label
 -                margin-left: 4px
 -        &.oe_form_required
 -            .oe_radio_input
 -                border: 2px solid transparent
                  display: inline-block
 -                height: 12px
 -                width: 12px
 -                vertical-align: top
 -                border-radius: 10px
 -                margin: 1px 0
 -            &.oe_form_invalid
 -                .oe_radio_input
 -                    border-color: red
 +                margin-left: 20px
 +        .oe_radio_input
 +            border: 2px solid transparent
 +            display: inline-block
 +            height: 12px
 +            width: 12px
 +            vertical-align: top
 +            border-radius: 10px
 +            margin: 1px 0
 +            position: absolute
 +        &.oe_form_required.oe_form_invalid .oe_radio_input
 +            border-color: red
      .oe_tags
          &.oe_inline
              min-width: 250px
          border-right: 4px solid transparent
          border-top: 4px solid#404040
          @include opacity(0.5)
 -    .oe_sidebar
 -        white-space: nowrap
 -        .oe_dropdown_menu
 -            .oe_sidebar_add_attachment
 -                height: 20px
 -                cursor: pointer
 -                padding-left: 6px
 -                margin-top: 6px
 -                span
 -                    font-weight: bold
 -                .oe_hidden_input_file
 -                    width: 200px
 -                &:hover
 -                    @include vertical-gradient(#f0f0fa, #eeeef6)
 -                    @include box-shadow(none)
 -            li
 -                .oe_sidebar_delete_item
 -                    position: absolute
 -                    top: 4px
 -                    right: 4px
 -                    display: none
 -                    width: 12px
 -                    height: 12px
 -                    padding: 1px
 -                    color: #8786b7
 -                    line-height: 8px
 -                    text-align: center
 -                    font-weight: bold
 -                    text-shadow: 0 1px 1px white
 -                    &:hover
 -                        text-decoration: none
 -                        color: white
 -                        background: #8786b7
 -                        text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4)
 -                        @include radius(2px)
 -                &:hover
 -                    .oe_sidebar_delete_item
 -                        display: inline-block
      // }}}
      // Loading {{{
      .oe_loading
          .oe_database_manager_menu
              color: #000
      // }}}
 -    // WebClient {{{
 -    .oe_webclient
 -        width: 100%
 -        height: 100%
 -        border-spacing: 0px
 -    // }}}
      // WebClient.fullscreen {{{
      .oe_content_full_screen
          .oe_application
      // }}}
      // Webclient.leftbar {{{
      .oe_leftbar
 -        display: none
 +        display: -ms-flexbox
 +        display: -webkit-flex
 +        display: flex
 +        -ms-flex-direction: column
 +        -webkit-flex-direction: column
 +        flex-direction: column
 +        -webkit-justify-content: space-between
 +        justify-content: space-between
 +        -ms-flex: 0 0 auto
 +        -webkit-flex-shrink: 0
 +        flex-shrink: 0
 +        -webkit-flex-grow: 0
 +        flex-grow: 0
          width: 220px
 +        height: 100%
          background: #f0eeee
          border-right: 1px solid $tag-border
 -        text-shadow: none
 -        > div
 -            height: 100%
 -            display: table
 -            width: 220px
 -            position: relative
 -            a.oe_logo
 -                display: table-row
 -                text-align: center
 -                img
 -                    margin: 14px 0
 -                    border: 0
 -                .oe_logo_edit
 -                    margin: 14px 0
 -                    position: absolute
 -                    top: 1px
 -                    padding: 4px
 -                    width: 100%
 -                    display: none
 -                    text-align: center
 -                    color: #eee
 -                    background: rgba(37,37,37,0.9)
 -                    @include box-sizing(border)
 -                &:hover .oe_logo_edit_admin
 -                    display: block
 -            > div
 -                display: table-row
 -                height: 100%
 -                > div
 -                    position: relative
 -                    height: 100%
 -                    > div.oe_secondary_menus_container
 -                        position: absolute
 -                        position: static\9
 -                        top: 0
 -                        bottom: 0
 -                        left: 0
 -                        right: 0
 -                        overflow-x: hidden
 -                        overflow-y: auto
 -            .oe_footer
 -                background: #f0eeee
 +        position: relative
 +        display: inline-block\9
 +        vertical-align: top\9
 +        a.oe_logo
 +            text-align: center
 +            -webkit-flex-shrink: 0
 +            flex-shrink: 0
 +            img
 +                margin: 14px 0
 +                border: 0
 +            .oe_logo_edit
 +                margin: 14px 0
 +                position: absolute
 +                top: 1px
 +                padding: 4px
 +                width: 100%
 +                display: none
                  text-align: center
 -                a
 -                    font-weight: bold
 -                    color: black
 -                    span
 -                        color: #a24689
 +                color: #eee
 +                background: rgba(37,37,37,0.9)
 +                @include box-sizing(border)
 +            &:hover .oe_logo_edit_admin
 +                display: block
 +        .oe_secondary_menus_container
 +            -webkit-flex-grow: 1
 +            flex-grow: 1
 +            overflow-y: auto
 +        .oe_footer
 +            background: #f0eeee
 +            text-align: center
 +            -webkit-flex-shrink: 0
 +            flex-shrink: 0
 +            a
 +                font-weight: bold
 +                span
 +                    color: #a24689
      // }}}
      // Webclient.leftbar items {{{
  
      a.oe_form_uri:hover
          text-decoration: underline
      .oe_application
 -        width: 100%
          height: 100%
 -        a
 -            color: $link-color
 -        > div
 -            position: relative
 -            height: 100%
 -            > .oe_view_manager
 -                > .oe_view_manager_wrapper
 -                    display: table-row
 -                    height: 100%
 -                    > div
 -                        position: relative
 -                        height: 100%
 -                        > .oe_view_manager_body
 -                            position: absolute
 -                            position: static\9
 -                            top: 0
 -                            bottom: 0
 -                            left: 0
 -                            right: 0
 -                            overflow: auto
 -
 -
 -        .oe_breadcrumb_item:not(:last-child)
 -            max-width: 7em
 -            white-space: nowrap
 -            text-overflow: ellipsis
 -        .oe_breadcrumb_title > *
 -            display: inline-block
 -            overflow: hidden
 -            font-weight: bold
 +        -webkit-flex-grow: 1
 +        flex-grow: 1
 +        -ms-flex-negative: 1
 +        display: inline-block\9
 +        overflow: auto\9
 +        width: -webkit-calc(100% - 220px)
 +        width: calc(100% - 220px)
 +        .oe_application
 +            width: 100%
      // }}}
      // ViewManager common {{{
 -    .oe_view_manager
 -        display: table
 -        height: inherit
 +    .oe-view-manager
          width: 100%
 -        .oe_view_manager_view_kanban:not(:empty)
 -            height: 100%
 -        &[data-view-type=kanban]
 -            .oe_view_manager_body
 -                display: table-row
 -
 -        table.oe_view_manager_header
 -            border-collapse: separate
 -            width: 100%
 -            table-layout: fixed
 -            .oe_header_row
 -                //min-height: 26px
 -                //line-height: 26px
 -                clear: both
 -                text-shadow: 0 1px 1px white
 -            .oe_header_row:last-child
 -                td
 -                    padding-top: 0
 -            .oe_header_row:first-child
 -                td
 -                    padding-top: 8px
 -            .oe_view_manager_sidebar
 -                margin: 0px auto
 -                text-align: center
 -                .oe_dropdown_arrow:after
 -                    opacity: 0.9
 -            td
 -                line-height: 26px
 -            h2
 -                font-size: 18px
 -                margin: 0
 -                float: left
 -                line-height: 30px
 +        height: 100%
 +        display: -webkit-flex
 +        display: flex
 +        -webkit-flex-direction: column
 +        flex-direction: column
 +        .oe-view-manager-content
 +            overflow: auto
 +            -webkit-flex-grow: 1
 +            flex-grow: 1
 +            position: relative
 +            a
 +                color: $link-color
 +            > div
 +                position: absolute
 +                position: static\9
 +                top: 0
 +                bottom: 0
 +                right: 0
 +                left: 0
 +                display: none
 +            .oe-view-manager-content
 +                > div
 +                    position: relative
 +                    display: block
 +        .oe-view-manager-debug
 +            margin-right: 5px
 +    .oe-view-manager-header
 +        background-color: rgb(240, 238, 238)
 +        border-bottom: 1px solid #afafb6
 +        -webkit-flex-shrink: 0
 +        flex-shrink: 0
 +        width: 100%
 +        -webkit-user-select: none
 +        -moz-user-select: none
 +        user-select: none
 +        .dropdown-menu
 +            li
 +                position: relative
                  a
 -                    color: $link-color
 -            .oe_dropdown_menu
 -                line-height: normal
 -            .oe_button_group
 +                    padding: 3px 25px
 +            .oe_searchview_custom_public
 +                a:after
 +                    font-family: FontAwesome
 +                    content: "\f0c0"
 +                    color: #666
 +                    margin-left: 3px
 +        .selected
 +            display: block
 +            a
 +                font-weight: bold
 +            a:before
 +                font-family: FontAwesome
 +                position: absolute
 +                left: 6px
 +                top: 3px
 +                content: "\f00c"
 +        .oe-right-toolbar
 +            float: right
 +            > div
                  display: inline-block
 -                border: 1px solid #ababab
 -                @include radius(5px)
 -                li
 -                    float: left
 -                    border-right: 1px solid #ababab
 -                    &:last-child
 -                        border: none
 -                a
 -                    color: #4c4c4c
 -                    &:hover
 -                        text-decoration: none
 -                .active
 -                    background: #999
 -                    @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset)
 -                    a
 -                        color: #fff
 -                        text-shadow: 0 1px 2px rgba(0,0,0,0.4)
 -            .oe_view_manager_buttons
 -                white-space: nowrap
 -        // }}}
 -        // ViewManager.switches {{{
 -        .oe_view_manager_switch
 -            padding: 0
 -            margin: 0 0 0 8px
 +        .row:first-child
 +            padding-top: 3px
 +            padding-bottom: 3px
 +        .row:last-child
 +            padding-bottom: 10px
 +        .oe_tag
 +            @include radius(0px)
 +        .oe-view-title
 +            font-size: 18px
 +            padding-left: 0
 +            margin: 0
 +            background-color: rgb(240, 238, 238)
              li
 -                margin: 0
 -                width: 24px
 -                height: 24px
 -                line-height: 16px
 +                -moz-user-select: initial
 +                -webkit-user-select: initial
 +                user-select: initial
 +        .oe-view-manager-search-view
 +            padding-top: 5px
 +        .oe-view-manager-switch
 +            .oe-vm-switch-kanban:before
 +                content: "\f009"
 +            .oe-vm-switch-list:before
 +                content: ""
 +            .oe-vm-switch-form:before
 +                content: "\f044"
 +            .oe-vm-switch-graph:before
 +                content: "\f080"
 +            .oe-vm-switch-calendar:before
 +                content: "\f073"
 +            .oe-vm-switch-gantt:before
 +                content: "\f0ae"
 +            .oe-vm-switch-pivot:before
 +                content: "\f0ce"
 +        .oe-view-manager-buttons
 +            display: inline-block
 +            > div
 +                display: none
 +        .oe-view-manager-sidebar
 +            display: inline-block
 +            float: right
 +            .oe_form_binary_form
 +                cursor: pointer
 +                span
 +                    padding: 3px 20px
 +                input.oe_form_binary_file
 +                    width: 100%
 +
 +            .oe_form_binary_form:hover
 +                background-color: #f5f5f5
 +            .oe_file_attachment
 +                padding: 3px 20px
 +                display: inline-block
 +            .oe_sidebar_delete_item
                  padding: 0
 -                text-align: center
 -                list-style-type: none
 -                a
 -                    position: relative
 -            .oe_vm_switch_list:after, .oe_vm_switch_tree:after
 -                padding: 2px
 -                content: "i"
 -            .oe_vm_switch_form:after
 -                content: "m"
 -            .oe_vm_switch_graph:after
 -                font-family: "mnmliconsRegular" !important
 -                font-size: 21px
 -                font-weight: 300 !important
 -                content: "}"
 -                top: -2px
 -                position: relative
 -            .oe_vm_switch_gantt:after
 -                font-family: "mnmliconsRegular" !important
 -                font-size: 21px
 -                font-weight: 300 !important
 -                content: "y"
 -                top: -2px
 -                position: relative
 -            .oe_vm_switch_calendar:after
 -                content: "P"
 -            .oe_vm_switch_kanban:after
 -                content: "k"
 -            .oe_vm_switch_diagram:after
 -                content: "f"
 -        // }}}
 -    // List pager {{{
 -    .oe_list_pager
 -        line-height: 26px
 -    .oe_pager_value
 -        float: left
 -        margin-right: 8px
 -    ul.oe_pager_group
 -        padding: 0
 -        margin: 0
 -    .oe_pager_group
 -        float: left
 -        height: 24px
 -        line-height: 24px
 -        display: inline-block
 -        border: 1px solid #ababab
 -        cursor: pointer
 -        @include radius(5px)
 -        li
 -            height: 24px
 -            line-height: 24px
 +                display: inline-block
 +            .dropdown-menu
 +                li
 +                    a
 +                        width: 100%
 +        .oe_form_buttons
              padding: 0
 -            margin: 0
 -            list-style-type: none
 -            float: left
 -            border-right: 1px solid #ababab
 -            &:last-child
 -                border: none
 -        a
 -            color: #4c4c4c
 -            padding: 0 8px
 -            &:hover
 -                text-decoration: none
 -        .active
 -            background: #999
 -            @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset)
 -            a
 -                color: #fff
 -                text-shadow: 0 1px 2px rgba(0,0,0,0.4)
 -    .oe_list_pager.oe_list_pager_single_page .oe_pager_group
 -        display: none
 -    // }}}
 -    // ViewManager application {{{
 -    .oe_view_manager_current
 -        height: 100%
 -        > .oe_view_manager_header
 -            border-bottom: 1px solid #cacaca
 -            @include vertical-gradient(#fcfcfc, #dedede)
 -            @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1)))
 -            .oe_header_row
 -                td
 -                    padding: 8px
 -            .oe_header_row:first-child
 -                td
 -                    padding-top: 8px
 -    // }}}
 -    // ViewManager inline {{{
 +        .oe_form_buttons_view
 +            > button
 +                float: left
 +                &:last-child
 +                    float: right
 +                    margin-left: 4px
 +
 +        .oe-pager-buttons
 +            min-height: 30px
      .oe_view_manager_inline, .oe_view_manager_inlineview
 -        height: 100%
 -        > .oe_view_manager_header
 +        > .oe-view-manager-header
              display: none
 -    // }}}
 +
 +        // }}}
      // FormPopup {{{
 +    .o-modal-header
 +        > div
 +            margin-left: 45%
      .oe_popup_form 
 -        display: table
 +        display: none
          .oe_formview .oe_form_pager
              display: none !important
          // Customize label weight for popup wizard appear from another wizard according bootstrap3
      .oe_searchview
          cursor: text
          position: relative
 -        float: right
 -        padding: 1px 0
 -        line-height: 18px
 -        min-width: 400px
 -        border: 1px solid #ababab
 -        background: white
 -        @include radius(13px)
 -        @include box-shadow(0 1px 2px rgba(0,0,0,0.2) inset)
 -        input, textarea
 -            padding: 3px
 -            height: 14px
 -            font-size: 12px
 -            line-height: 18px
 -        //Customize searchview input, select
 -        input:not([type]), input[type="text"], input[type="number"]
 -            width: 156px
 -            height: 22px
 -        input[type="checkbox"]
 -            margin: 3px 3px 3px 4px
 -        select
 -            margin: 2px 4px 2px 0 
 -        //End of customize
 -        &.oe_focused
 -            border-color: $tag-border-selected
 -            @include box-shadow(0 1px 2px $tag-border-selected inset)
 -        .oe_searchview_clear
 -            cursor: pointer
 -            position: absolute
 -            top: 0
 -            right: 18px
 -            width: 15px
 -            height: 24px
 -            background: url(../img/search_reset.gif) center center no-repeat
 +        display: none
 +        height: auto
 +        padding-top: 1px
 +        padding-bottom: 1px
          .oe_searchview_unfold_drawer
              position: absolute
 -            top: 0
 -            right: 0
 -            height: 24px
 -            padding: 0 7px 0 4px
 -            color: #ccc
 +            top: 8px
 +            right: 5px
              cursor: pointer
 -            &:hover
 -                color: #999
 -            &:before
 -                position: absolute
 -                top: 10px
 -                right: 7px
 -                width: 0
 -                height: 0
 -                display: inline-block
 -                content: ""
 -                vertical-align: top
 -                border-top: 5px solid #4C4C4C
 -                border-left: 5px solid transparent
 -                border-right: 5px solid transparent
 -                @include opacity()
 -
          .oe_searchview_search
 -            @include text-to-icon("r", #a3a3a3)
 -            @include box-shadow(none)
 -            @include radius(0)
 +            cursor: pointer
              position: absolute
 -            left: 3px
 -            top: 1px
 -            padding: 0
 -            border: none
 -            background: transparent
 -
 +            top: 8px
 +            left: 5px
          .oe_searchview_facets
              min-height: 22px
 -            margin: 0 35px 0 15px
 +            margin: 2px 20px 0 0
              *
                  vertical-align: top
                  display: inline-block
 -                line-height: 17px
 +                font-size: 100%
              .oe_searchview_facet
                  margin: 1px 0
                  font-size: 11px
                  &:focus
                      outline: none
 +                .label
 +                    @include radius(0px)
              .oe_searchview_input
 -                padding: 0 0 0 6px
 +                padding: 2px 0 0 6px
                  font-size: 12px
                  height: 16px
 -                margin-top: 3px
 +                -webkit-user-select: initial
 +                -moz-user-select: initial
 +                user-select: initial
                  &:focus
                      outline: none
 +            .oe_searchview_input:first-child
 +                margin-left: 10px
              .oe_searchview_facet
                  position: relative
                  cursor: pointer
                      @include box-shadow(0 0 3px 1px $tag-border-selected)
                  .oe_facet_values
                      background: $tag-bg-light
 -                    @include radius(0 3px 3px 0)
                  .oe_facet_category, .oe_facet_value
                      padding: 0 4px
                  .oe_facet_category
                  .oe_facet_category.oe_i
                      font-size: 16px
                  .oe_facet_value
 -                    border-left: 1px solid $tag-border
 -                    text-shadow: 0 1px 1px white
 +                    border-left: 1px solid #afafb6
                      color: #4C4C4C
                      &:last-child
                          padding-right: 16px
 +                    &:first-child
 +                        border-left: none
                  .oe_facet_remove
                      position: absolute
                      top: 3px
          .oe-autocomplete
              display: none
              position: absolute
 -            width: 300px
              background-color: white
 +            width: 400px
              border: 1px solid #afafb6
              z-index: 666
              margin-top: 2px
              cursor: default
              @include radius(3px)
 -            @include box-shadow(0 1px 4px rgba(0, 0, 0, 0.3))
              ul
                  list-style-type: none
                  padding-left: 0
                  li.oe-separator:last-child
                      display: none
  
 -
 -    .oe_searchview_drawer_container
 -        overflow: auto
 -    .oe_searchview_drawer
 -        display: none
 -        width: 100%
 -        cursor: default
 -        display: none
 -        overflow: hidden
 -        border-bottom: 1px solid $tag-border
 -        text-align: left
 -        padding: 8px 0
 -        .badge
 -            font-size: 12px
 -            line-height: 12px
 -        > div:first-child
 -            border: none
 -            padding-left: 0
 -            li:hover:not(.badge)
 -                background-color: $hover-background
 -        .col-md-5
 +    .oe-search-options
 +        a
 +            padding-left: 25px
 +            padding-right: 25px
 +            width: 100%
 +            -webkit-user-select: none
 +            -moz-user-select: none
 +            user-select: none
 +        .oe-apply-filter
 +            margin-left: 25px
 +        .oe-add-filter-menu
 +            display: none
 +        .oe-add-condition
 +            width: auto
 +            margin-left: 10px
              padding-left: 0
 -        dl 
 -            margin-bottom: 0
 -        dt
 -            color: $section-title-color
 -            font-size: 13px
 -            line-height: 24px
 -        dd
 -            line-height: 24px
 -            font-size: 13px
 -            padding-top: 3px
 -        h4, h4 *
 -            margin: 0 0 0 2px
 -            padding-left: 20px
 -            cursor: pointer
 -            font-weight: normal
 -            display: inline-block
 -            &:hover
 -                background-color: $hover-background
 -        h4:before
 -            content: "▸ "
 -            color: #a3a3a3
 -        button
 -            margin: 4px 0
 -        .button
 -            border: none
 -            background: transparent
 -            padding: 0 2px
 -            @include box-shadow(none)
 -            @include radius(0)
 -        .oe_searchview_section
 -            ul
 -                margin: 0 8px 
 -                padding: 0
 -                list-style: none
 -                display: inline
 -            li
 -                display: inline-block
 -                cursor: pointer
 -                position: relative
 -                margin-right: 8px
 -                > span
 -                    display: inline-block
 -                    max-width: 250px
 -                    text-overflow: ellipsis
 -                    vertical-align: bottom
 -                    overflow: hidden
 -        form
 -            margin-left: 12px
 -            p
 -                margin: 4px 0
 -                line-height: 18px
 -            button
 -                margin: 0 0 8px -3px // Managed margin-left according bootstrap3
 -        .oe_searchview_savefilter
 -            form
 -                display: none
 -        .oe_searchview_custom
 +            padding-right: 10px
 +        .oe-group-selector
 +            margin: 3px 25px
 +            min-width: -webkit-calc(100% - 40px)
 +            min-width: calc(100% - 40px)
 +            max-width: 250px
 +            width: auto
 +        .divider
              display: none
 -            li
 -                cursor: pointer
 -                position: relative
 -                line-height: 14px
 -                margin-right: 0
 -                button
 -                    position: absolute
 -                    top: 0
 -                    right: 5px
 -                a
 -                    margin-left: 10px
 -                    position: inherit
 -                    visibility: hidden
 -                    display: inline-block
 -                span:hover:not(.badge)
 -                    background-color: $hover-background
 -            li:hover a
 -                visibility: visible
 -            //Customize for searchview label
 -            label
 -                font-weight: normal
 -            //End of Customize
 -        .oe_searchview_dashboard
 -            form
 -                display: none
 -                margin-top: 2px
 -
 -        .oe_searchview_advanced
 +        .filters-menu
              overflow: auto
 -            form
 -                display: none
 -                margin-top: 8px
 -            button.oe_add_condition:before
 -                content: "Z"
 -                font-family: "entypoRegular" !important
 -                font-size: 24px
 -                font-weight: 300 !important
 -                margin-right: 4px
 -            ul
 -                list-style: none
 -                padding: 0
 -            li
 -                position: relative
 -                list-style: none
 -                margin: 0
 -                white-space: nowrap
 -                &:first-child .searchview_extended_prop_or
 -                    visibility: hidden
 -                    margin-left: -14px
 -            .searchview_extended_prop_or
 -                opacity: 0.5
 -                margin-left: -14px //Customize 'or' in searchview
 -        .oe_opened
 -            h4:before
 -                content: "▾ "
 -                position: relative
 -                top: -1px
 -            form
 -                display: block
 +            overflow-x: hidden
 +            .divider
 +                display: list-item
 +        .closed-menu
 +            a:before
 +                font-family: FontAwesome
 +                position: absolute
 +                left: 12px
 +                top: 3px
 +                content: "\f0da"
 +        .open-menu
 +            a:before
 +                font-family: FontAwesome
 +                position: absolute
 +                left: 9px
 +                top: 3px
 +                content: "\f0d7"
  
 -        // delete buttons
 -        .oe_searchview_custom_delete, .searchview_extended_delete_prop
 -            display: inline-block
 -            width: 12px
 -            height: 12px
 -            line-height: 12px
 -            padding: 1px
 -            color: #8786b7
 -            line-height: 8px
 -            text-align: center
 -            font-weight: bold
 -            text-shadow: 0 1px 1px white
 -            &:hover
 -                text-decoration: none
 -                color: white
 -                background: #8786b7
 -                text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4)
 -                @include radius(2px)
 -        .oe_searchview_custom_delete
 +        .oe-select-group
 +            margin: 3px 25px
 +        .oe-add-group
              display: none
 +        .oe-save-name
 +            display: none
 +            margin: 3px 25px
 +            span
 +                white-space: nowrap
 +                input
 +                    margin-left: 0
 +        .searchview_extended_prop_field
 +            display: inline
 +            width: -webkic-calc(100% - 20px)
 +            width: calc(100% - 20px)
 +        
 +        .searchview_extended_prop_op
 +            margin: 3px 0
 +            width: -webkit-calc(100% - 20px)
 +            width: calc(100% - 20px)
 +        .searchview_extended_delete_prop
 +            float: right
 +            display: inline
 +            cursor: pointer
 +        .searchview_extended_prop_value
 +            width: -webkit-calc(100% - 20px)
 +            width: calc(100% - 20px)
 +            > select, > input
 +                width: -webkit-calc(100% - 20px)
 +                width: calc(100% - 20px)
 +            .oe_datepicker_main
 +                width: -webkit-calc(100% - 20px)
 +                width: calc(100% - 20px)
 +            .oe_datepicker_master
 +                width: 100%
 +            .fa-calendar
 +                margin-left: -21px
 +                cursor: pointer
 +        span.remove-filter
              position: absolute
 -            bottom: 1px
 -            right: 4px
 -        .oe_searchview_custom_private, .oe_searchview_custom_public
 -            &:hover
 -                .oe_searchview_custom_delete
 -                    display: inline-block
 -        .oe_searchview_custom_public:after
 -            content: ","
 -            font-family: "entypoRegular" !important
 -            font-size: 22px
 -            font-weight: 300 !important
 -            margin: 0 0 0 4px
 -            padding: 0
 +            top: 6px
 +            right: 10px
 +            cursor: pointer
 +        .dropdown-menu
 +            > li
 +                position: relative
 +                white-space: nowrap
 +            li.oe-filter-condition
 +                white-space: normal
 +                padding-left: 25px
 +                padding-right: 10px
 +                margin-bottom: 5px
 +                .o-or-filter
 +                    display: none
 +            li.oe-filter-condition + li.oe-filter-condition
 +                margin-top: 10px
 +                .o-or-filter
 +                    display: block
 +            .o-or-filter
 +                position: absolute
 +                left: 5px
 +                top: 5px
 +        a
 +            display: inline-block
 +
 +    input.oe-save-name
 +        width: auto
 +    .oe_pager_value
 +        display: inline-block  
 +
      // }}}
      // Views Common {{{
      .oe_view_nocontent
              td.oe_form_group_cell_label
                  border-right: none
              .oe_subtotal_footer_separator
-                 width: 108px
+                 min-width: 108px
                  border-top: 1px solid #cacaca
                  margin-top: 4px
                  padding-top: 4px
              white-space: nowrap
          .oe_form_field_boolean
              width: auto
 -        .oe_datepicker_container
 -            display: none
          .oe_datepicker_root
              display: inline-block
 +            .datepickerbutton
 +                margin-left: 5px
 +                cursor: pointer
          .oe_form_required
              input:not([disabled]):not([readonly]), select:not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly])
                  background-color: #D2D2FF !important
              .oe_view_manager_view_list
                  min-height: 132px
  
 -        .oe_form_field_one2many_list_row_add
 +        .oe_form_field_one2many_list_row_add,.oe_form_field_many2many_list_row_add
              font-weight: bold
          .oe_list_content
              > thead
              content: "[newline]"
      .oe_debug_view
          float: left
 +        margin-top: 5px
 +        width: auto
      .oe_debug_view_log
          font-size: 95%
          line-height: 1.2em
          background-color: #7c7bad
  
      // Customize for global tags
 -    button, body
 -        line-height: normal
      h1,h2
          font-weight: bold
      h3
          
  @-moz-document url-prefix()
      .openerp
 -        .oe_searchview .oe_searchview_search
 -            top: -1px
          .oe_form_field_many2one .oe_m2o_cm_button
              line-height: 18px
          .oe_webclient
@@@ -2554,31 -2747,13 +2554,31 @@@ body.oe_single_for
          height: 18px
          font-size: 20px
      .modal-body
 -        overflow-x: auto
 +        overflow: auto
 +        padding: 0
 +        .oe_application
 +            width: 100%
 +        .oe_popup_list
 +            display: none
 +            min-height: 150px
 +        .oe-view-manager
 +            min-height: 150px
 +        .oe_form_sheetbg
 +            .oe_form_sheet
 +                padding: 0
      .modal-footer
          text-align: left
      .oe_button
          margin: 0 4px 0 0
      .oe_act_window.modal-body
          padding: 0
 +    .oe-view-manager-content > div
 +        position: static !important
 +
 +@media (min-width: 768px)
 +    .modal .modal-body
 +        max-height: -webkit-calc(100vh - 170px)
 +        max-height: calc(100vh - 170px)
  
  .ui-datepicker
      z-index: 1500 !important
@@@ -2622,13 -2797,11 +2622,13 @@@ div.tour-backdro
  body
      overflow: hidden
  
 -// hack to make IE11 work
  @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) 
 -    .oe_secondary_menus_container 
 -        position: static !important
 -
 +    .oe-view-manager-content
 +        > div
 +            position: absolute !important
 +        .oe-view-manager-content
 +            > div
 +                position: relative !important
  
  
  // au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css"
@@@ -125,16 -125,14 +125,16 @@@ instance.web.Dialog = instance.web.Widg
          var $customButons = this.$buttons.find('.oe_dialog_custom_buttons').empty();
          _.each(buttons, function(fn, text) {
              // buttons can be object or array
 +            var pre_text  = fn.pre_text || "";
 +            var post_text = fn.post_text || "";
              var oe_link_class = fn.oe_link_class;
              if (!_.isFunction(fn)) {
                  text = fn.text;
                  fn = fn.click;
              }
 -            var $but = $(QWeb.render('WidgetButton', { widget : { string: text, node: { attrs: {'class': oe_link_class} }}}));
 +            var $but = $(QWeb.render('WidgetButton', { widget : { pre_text: pre_text, post_text: post_text, string: text, node: { attrs: {'class': oe_link_class} }}}));
              $customButons.append($but);
 -            $but.on('click', function(ev) {
 +            $but.filter('button').on('click', function(ev) {
                  fn.call(self.$el, ev);
              });
          });
              delete(options.buttons);
          }
          this.renderElement();
 -
          this.$dialog_box = $(QWeb.render('Dialog', options)).appendTo("body");
          this.$el.modal({
              'backdrop': false,
@@@ -344,14 -343,13 +344,14 @@@ instance.web.RedirectWarningHandler = i
              size: 'medium',
              title: "Odoo " + (_.str.capitalize(error.type) || "Warning"),
              buttons: [
 -                {text: _t("Ok"), click: function() { self.$el.parents('.modal').modal('hide');  self.destroy();}},
 -                {text: error.data.arguments[2],
 -                 oe_link_class: 'oe_link',
 -                 click: function() {
 -                    window.location.href='#action='+error.data.arguments[1];
 -                    self.destroy();
 -                }}
 +                {text: error.data.arguments[2], 
 +                    oe_link_class : 'oe_highlight',
 +                    post_text : _t("or"),
 +                    click: function() {
 +                        window.location.href='#action='+error.data.arguments[1]
 +                        self.destroy();
 +                    }},
 +                {text: _t("Cancel"), oe_link_class: 'oe_link', click: function() { self.$el.parents('.modal').modal('hide');  self.destroy();}}
              ],
          }, QWeb.render('CrashManager.warning', {error: error})).open();
      }
@@@ -1172,13 -1170,6 +1172,13 @@@ instance.web.Client = instance.web.Widg
              }
          });
      },
 +    on_logo_click: function(ev){
 +        if (!this.has_uncommitted_changes()) {
 +            return;
 +        } else {
 +            ev.preventDefault();
 +        }
 +    },
      show_common: function() {
          var self = this;
          this.crashmanager =  new instance.web.CrashManager();
          self.loading = new instance.web.Loading(self);
          self.loading.appendTo(self.$('.openerp_webclient_container'));
          self.action_manager = new instance.web.ActionManager(self);
 -        self.action_manager.appendTo(self.$('.oe_application'));
 +        self.action_manager.replace(self.$('.oe_application'));
      },
      toggle_bars: function(value) {
          this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
@@@ -1283,9 -1274,6 +1283,9 @@@ instance.web.WebClient = instance.web.C
              self.logo_edit(ev);
          });
  
 +        this.$('.oe_logo img').click(function(ev) {
 +              self.on_logo_click(ev);
 +          });
          // Menu is rendered server-side thus we don't want the widget to create any dom
          self.menu = new instance.web.Menu(self);
          self.menu.setElement(this.$el.parents().find('.oe_application_menu_placeholder'));
          }
      },
      update_logo: function() {
-         var img = this.session.url('/web/binary/company_logo');
+         var company = this.session.company_id;
+         var img = this.session.url('/web/binary/company_logo' + (company ? '?company=' + company : ''));
          this.$('.oe_logo img').attr('src', '').attr('src', img);
          this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', this.session.uid === 1);
      },
@@@ -7,6 -7,9 +7,6 @@@ openerp.web.search = {}
  var QWeb = instance.web.qweb,
        _t =  instance.web._t,
       _lt = instance.web._lt;
 -_.mixin({
 -    sum: function (obj) { return _.reduce(obj, function (a, b) { return a + b; }, 0); }
 -});
  
  /** @namespace */
  var my = instance.web.search = {};
@@@ -327,15 -330,18 +327,15 @@@ instance.web.SearchView = instance.web.
              }
          },
          // search button
 -        'click button.oe_searchview_search': function (e) {
 +        'click div.oe_searchview_search': function (e) {
              e.stopImmediatePropagation();
              this.do_search();
          },
 -        'click .oe_searchview_clear': function (e) {
 -            e.stopImmediatePropagation();
 -            this.query.reset();
 -        },
          'click .oe_searchview_unfold_drawer': function (e) {
              e.stopImmediatePropagation();
 -            if (this.drawer) 
 -                this.drawer.toggle();
 +            $(e.target).toggleClass('fa-caret-down fa-caret-up');
 +            localStorage.visible_search_menu = (localStorage.visible_search_menu !== 'true');
 +            this.toggle_buttons();
          },
          'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
              switch(e.which) {
              disable_custom_filters: false,
          });
          this._super(parent);
 +        this.query = undefined;   
          this.dataset = dataset;
 -        this.model = dataset.model;
          this.view_id = view_id;
 -
 +        this.search_fields = [];
 +        this.filters = [];
 +        this.groupbys = [];
 +        this.visible_filters = (localStorage.visible_search_menu === 'true');
 +        this.input_subviews = []; // for user input in searchbar
          this.defaults = defaults || {};
 -        this.has_defaults = !_.isEmpty(this.defaults);
 -
 -        this.headless = this.options.hidden && !this.has_defaults;
 -
 -        this.input_subviews = [];
 -        this.view_manager = null;
 -        this.$view_manager_header = null;
 -
 -        this.ready = $.Deferred();
 -        this.drawer_ready = $.Deferred();
 -        this.fields_view_get = $.Deferred();
 -        this.drawer = new instance.web.SearchViewDrawer(parent, this);
 -
 -    },
 +        this.headless = this.options.hidden &&  _.isEmpty(this.defaults);
 +        this.$buttons = this.options.$buttons;
 +
 +        this.filter_menu = undefined;
 +        this.groupby_menu = undefined;
 +        this.favorite_menu = undefined;
 +        this.action_id = this.options && this.options.action && this.options.action.id;
 +    },    
      start: function() {
 -        var self = this;
 -        var p = this._super();
 -
 -        this.$view_manager_header = this.$el.parents(".oe_view_manager_header").first();
 -
 +        if (this.headless) {
 +            this.$el.hide();
 +        }
 +        this.toggle_visibility(false);
 +        this.$facets_container = this.$('div.oe_searchview_facets');
          this.setup_global_completion();
          this.query = new my.SearchQuery()
                  .on('add change reset remove', this.proxy('do_search'))
                  .on('change', this.proxy('renderChangedFacets'))
                  .on('add reset remove', this.proxy('renderFacets'));
 +        var load_view = instance.web.fields_view_get({
 +            model: this.dataset._model,
 +            view_id: this.view_id,
 +            view_type: 'search',
 +            context: this.dataset.get_context(),
 +        });
 +        this.$('.oe_searchview_unfold_drawer')
 +            .toggleClass('fa-caret-down', !this.visible_filters)
 +            .toggleClass('fa-caret-up', this.visible_filters);
 +        return this.alive($.when(this._super(), load_view.then(this.view_loaded.bind(this))));
 +    },
 +    view_loaded: function (r) {
 +        var self = this;
 +        this.fields_view_get = r;
 +        this.view_id = this.view_id || r.view_id;
 +        this.prepare_search_inputs();
 +        if (this.$buttons) {
  
 -        if (this.options.hidden) {
 -            this.$el.hide();
 -        }
 -        if (this.headless) {
 -            this.ready.resolve();
 -        } else {
 -            var load_view = instance.web.fields_view_get({
 -                model: this.dataset._model,
 -                view_id: this.view_id,
 -                view_type: 'search',
 -                context: this.dataset.get_context(),
 -            });
 +            var fields_def = new instance.web.Model(this.dataset.model).call('fields_get', {
 +                    context: this.dataset.context
 +                });
  
 -            this.alive($.when(load_view)).then(function (r) {
 -                self.fields_view_get.resolve(r);
 -                return self.search_view_loaded(r);
 -            }).fail(function () {
 -                self.ready.reject.apply(null, arguments);
 -            });
 -        }
 +            this.groupby_menu = new my.GroupByMenu(this, this.groupbys, fields_def);
 +            this.filter_menu = new my.FilterMenu(this, this.filters, fields_def);
 +            this.favorite_menu = new my.FavoriteMenu(this, this.query, this.dataset.model, this.action_id);
  
 -        var view_manager = this.getParent();
 -        while (!(view_manager instanceof instance.web.ViewManager) &&
 -                view_manager && view_manager.getParent) {
 -            view_manager = view_manager.getParent();
 +            this.filter_menu.appendTo(this.$buttons);
 +            this.groupby_menu.appendTo(this.$buttons);
 +            var custom_filters_ready = this.favorite_menu.appendTo(this.$buttons);
          }
 -
 -        if (view_manager) {
 -            this.view_manager = view_manager;
 -            view_manager.on('switch_mode', this, function (e) {
 -                self.drawer.toggle(e === 'graph');
 -            });
 +        return $.when(custom_filters_ready).then(this.proxy('set_default_filters'));
 +    },
 +    // it should parse the arch field of the view, instantiate the corresponding 
 +    // filters/fields, and put them in the correct variables:
 +    // * this.search_fields is a list of all the fields,
 +    // * this.filters: groups of filters
 +    // * this.group_by: group_bys
 +    prepare_search_inputs: function () {
 +        var self = this,
 +            arch = this.fields_view_get.arch;
 +
 +        var filters = [].concat.apply([], _.map(arch.children, function (item) {
 +            return item.tag !== 'group' ? eval_item(item) : item.children.map(eval_item);
 +        }));
 +        function eval_item (item) {
 +            var category = 'filters';
 +            if (item.attrs.context) {
 +                try {
 +                    var context = instance.web.pyeval.eval('context', item.attrs.context);
 +                    if (context.group_by) {
 +                        category = 'group_by';
 +                    }                    
 +                } catch (e) {}
 +            }
 +            return {
 +                item: item,
 +                category: category,
 +            }
          }
 -        return $.when(p, this.ready);
 -    },
 +        var current_group = [],
 +            current_category = 'filters',
 +            categories = {filters: this.filters, group_by: this.groupbys};
  
 -    set_drawer: function (drawer) {
 -        this.drawer = drawer;
 +        _.each(filters.concat({category:'filters', item: 'separator'}), function (filter) {
 +            if (filter.item.tag === 'filter' && filter.category === current_category) {
 +                return current_group.push(new my.Filter(filter.item, self));
 +            }
 +            if (current_group.length) {
 +                var group = new my.FilterGroup(current_group, self);
 +                categories[current_category].push(group);
 +                current_group = [];
 +            }
 +            if (filter.item.tag === 'field') {
 +                var attrs = filter.item.attrs,
 +                    field = self.fields_view_get.fields[attrs.name],
 +                    Obj = my.fields.get_any([attrs.widget, field.type]);
 +                if (Obj) {
 +                    self.search_fields.push(new (Obj) (filter.item, field, self));
 +                }
 +            }
 +            if (filter.item.tag === 'filter') {
 +                current_group.push(new my.Filter(filter.item, self));
 +            }
 +            current_category = filter.category;
 +        });
      },
 -
 -    show: function () {
 -        this.$el.show();
 +    set_default_filters: function () {
 +        var self = this,
 +            default_custom_filter = this.$buttons && this.favorite_menu.get_default_filter();
 +        if (default_custom_filter) {
 +            return this.favorite_menu.toggle_filter(default_custom_filter, true);
 +        }
 +        if (!_.isEmpty(this.defaults)) {
 +            var inputs = this.search_fields.concat(this.filters, this.groupbys),
 +                defaults = _.invoke(inputs, 'facet_for_defaults', this.defaults);
 +            return $.when.apply(null, defaults).then(function () {
 +                self.query.reset(_(arguments).compact(), {preventSearch: true});
 +            });
 +        } 
 +        this.query.reset([], {preventSearch: true});
 +        return $.when();
      },
 -    hide: function () {
 -        this.$el.hide();
 +    /**
 +     * Performs the search view collection of widget data.
 +     *
 +     * If the collection went well (all fields are valid), then triggers
 +     * :js:func:`instance.web.SearchView.on_search`.
 +     *
 +     * If at least one field failed its validation, triggers
 +     * :js:func:`instance.web.SearchView.on_invalid` instead.
 +     *
 +     * @param [_query]
 +     * @param {Object} [options]
 +     */
 +    do_search: function (_query, options) {
 +        if (options && options.preventSearch) {
 +            return;
 +        }
 +        var search = this.build_search_data();
 +        this.trigger('search_data', search.domains, search.contexts, search.groupbys);
      },
 +    /**
 +     * Extract search data from the view's facets.
 +     *
 +     * Result is an object with 3 (own) properties:
 +     *
 +     * domains
 +     *     Array of domains
 +     * contexts
 +     *     Array of contexts
 +     * groupbys
 +     *     Array of domains, in groupby order rather than view order
 +     *
 +     * @return {Object}
 +     */
 +    build_search_data: function () {
 +        var domains = [], contexts = [], groupbys = [];
  
 -    subviewForRoot: function (subview_root) {
 -        return _(this.input_subviews).detect(function (subview) {
 -            return subview.$el[0] === subview_root;
 +        this.query.each(function (facet) {
 +            var field = facet.get('field');
 +            var domain = field.get_domain(facet);
 +            if (domain) {
 +                domains.push(domain);
 +            }
 +            var context = field.get_context(facet);
 +            if (context) {
 +                contexts.push(context);
 +            }
 +            var group_by = field.get_groupby(facet);
 +            if (group_by) {
 +                groupbys.push.apply(groupbys, group_by);
 +            }
          });
 +        return {
 +            domains: domains,
 +            contexts: contexts,
 +            groupbys: groupbys,
 +        };
 +    }, 
 +    toggle_visibility: function (is_visible) {
 +        this.$el.toggle(!this.headless && is_visible);
 +        this.$buttons && this.$buttons.toggle(!this.headless && is_visible && this.visible_filters);
      },
 -    siblingSubview: function (subview, direction, wrap_around) {
 -        var index = _(this.input_subviews).indexOf(subview) + direction;
 -        if (wrap_around && index < 0) {
 -            index = this.input_subviews.length - 1;
 -        } else if (wrap_around && index >= this.input_subviews.length) {
 -            index = 0;
 -        }
 -        return this.input_subviews[index];
 -    },
 -    focusPreceding: function (subview_root) {
 -        return this.siblingSubview(
 -            this.subviewForRoot(subview_root), -1, true)
 -                .$el.focus();
 +    toggle_buttons: function (is_visible) {
 +        this.visible_filters = is_visible || !this.visible_filters;
 +        this.$buttons && this.$buttons.toggle(this.visible_filters);
      },
 -    focusFollowing: function (subview_root) {
 -        return this.siblingSubview(
 -            this.subviewForRoot(subview_root), +1, true)
 -                .$el.focus();
 -    },
 -
      /**
       * Sets up search view's view-wide auto-completion widget
       */
      setup_global_completion: function () {
          var self = this;
 -        this.autocomplete = new instance.web.search.AutoComplete(this, {
 +        this.autocomplete = new my.AutoComplete(this, {
              source: this.proxy('complete_global_search'),
              select: this.proxy('select_completion'),
              delay: 0,
              get_search_string: function () {
                  return self.$('div.oe_searchview_input').text();
              },
 -            width: this.$el.width(),
          });
          this.autocomplete.appendTo(this.$el);
      },
       * @param {Function} resp response callback
       */
      complete_global_search:  function (req, resp) {
 -        $.when.apply(null, _(this.drawer.inputs).chain()
 +        var inputs = this.search_fields.concat(this.filters, this.groupbys);
 +        $.when.apply(null, _(inputs).chain()
              .filter(function (input) { return input.visible(); })
              .invoke('complete', req.term)
              .value()).then(function () {
                      .value());
                  });
      },
 -
      /**
       * Action to perform in case of selection: create a facet (model)
       * and add it to the search collection
       */
      select_completion: function (e, ui) {
          e.preventDefault();
 -
          var input_index = _(this.input_subviews).indexOf(
              this.subviewForRoot(
                  this.$('div.oe_searchview_input:focus')[0]));
          this.query.add(ui.item.facet, {at: input_index / 2});
      },
 -    childFocused: function () {
 -        this.$el.addClass('oe_focused');
 +    subviewForRoot: function (subview_root) {
 +        return _(this.input_subviews).detect(function (subview) {
 +            return subview.$el[0] === subview_root;
 +        });
      },
 -    childBlurred: function () {
 -        var val = this.$el.val();
 -        this.$el.val('');
 -        this.$el.removeClass('oe_focused')
 -                     .trigger('blur');
 -        this.autocomplete.close();
 +    siblingSubview: function (subview, direction, wrap_around) {
 +        var index = _(this.input_subviews).indexOf(subview) + direction;
 +        if (wrap_around && index < 0) {
 +            index = this.input_subviews.length - 1;
 +        } else if (wrap_around && index >= this.input_subviews.length) {
 +            index = 0;
 +        }
 +        return this.input_subviews[index];
      },
 -    /**
 -     * Call the renderFacets method with the correct arguments.
 -     * This is due to the fact that change events are called with two arguments
 -     * (model, options) while add, reset and remove events are called with
 -     * (collection, model, options) as arguments
 -     */
 -    renderChangedFacets: function (model, options) {
 -        this.renderFacets(undefined, model, options);
 +    focusPreceding: function (subview_root) {
 +        return this.siblingSubview(
 +            this.subviewForRoot(subview_root), -1, true)
 +                .$el.focus();
 +    },
 +    focusFollowing: function (subview_root) {
 +        return this.siblingSubview(
 +            this.subviewForRoot(subview_root), +1, true)
 +                .$el.focus();
      },
      /**
       * @param {openerp.web.search.SearchQuery | undefined} Undefined if event is change
      renderFacets: function (collection, model, options) {
          var self = this;
          var started = [];
 -        var $e = this.$('div.oe_searchview_facets');
          _.invoke(this.input_subviews, 'destroy');
          this.input_subviews = [];
  
          var i = new my.InputView(this);
 -        started.push(i.appendTo($e));
 +        started.push(i.appendTo(this.$facets_container));
          this.input_subviews.push(i);
          this.query.each(function (facet) {
              var f = new my.FacetView(this, facet);
 -            started.push(f.appendTo($e));
 +            started.push(f.appendTo(self.$facets_container));
              self.input_subviews.push(f);
  
              var i = new my.InputView(this);
 -            started.push(i.appendTo($e));
 +            started.push(i.appendTo(self.$facets_container));
              self.input_subviews.push(i);
          }, this);
          _.each(this.input_subviews, function (childView) {
              input_to_focus.$el.focus();
          });
      },
 -
 -    search_view_loaded: function(data) {
 -        var self = this;
 -        this.fields_view = data;
 +    childFocused: function () {
 +        this.$el.addClass('active');
+         this.view_id = this.view_id || data.view_id;
 -        if (data.type !== 'search' ||
 -            data.arch.tag !== 'search') {
 -                throw new Error(_.str.sprintf(
 -                    "Got non-search view after asking for a search view: type %s, arch root %s",
 -                    data.type, data.arch.tag));
 -        }
 -
 -        return this.drawer_ready
 -            .then(this.proxy('setup_default_query'))
 -            .then(function () { 
 -                self.trigger("search_view_loaded", data);
 -                self.ready.resolve();
 -            });
 -    },
 -    setup_default_query: function () {
 -        // Hacky implementation of CustomFilters#facet_for_defaults ensure
 -        // CustomFilters will be ready (and CustomFilters#filters will be
 -        // correctly filled) by the time this method executes.
 -        var custom_filters = this.drawer.custom_filters.filters;
 -        if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) {
 -            // Check for any is_default custom filter
 -            var personal_filter = _(custom_filters).find(function (filter) {
 -                return filter.user_id && filter.is_default;
 -            });
 -            if (personal_filter) {
 -                this.drawer.custom_filters.toggle_filter(personal_filter, true);
 -                return;
 -            }
 -
 -            var global_filter = _(custom_filters).find(function (filter) {
 -                return !filter.user_id && filter.is_default;
 -            });
 -            if (global_filter) {
 -                this.drawer.custom_filters.toggle_filter(global_filter, true);
 -                return;
 -            }
 -        }
 -        // No custom filter, or no is_default custom filter, apply view defaults
 -        this.query.reset(_(arguments).compact(), {preventSearch: true});
 -    },
 -    /**
 -     * Extract search data from the view's facets.
 -     *
 -     * Result is an object with 4 (own) properties:
 -     *
 -     * errors
 -     *     An array of any error generated during data validation and
 -     *     extraction, contains the validation error objects
 -     * domains
 -     *     Array of domains
 -     * contexts
 -     *     Array of contexts
 -     * groupbys
 -     *     Array of domains, in groupby order rather than view order
 -     *
 -     * @return {Object}
 -     */
 -    build_search_data: function () {
 -        var domains = [], contexts = [], groupbys = [], errors = [];
 -
 -        this.query.each(function (facet) {
 -            var field = facet.get('field');
 -            try {
 -                var domain = field.get_domain(facet);
 -                if (domain) {
 -                    domains.push(domain);
 -                }
 -                var context = field.get_context(facet);
 -                if (context) {
 -                    contexts.push(context);
 -                }
 -                var group_by = field.get_groupby(facet);
 -                if (group_by) {
 -                    groupbys.push.apply(groupbys, group_by);
 -                }
 -            } catch (e) {
 -                if (e instanceof instance.web.search.Invalid) {
 -                    errors.push(e);
 -                } else {
 -                    throw e;
 -                }
 -            }
 -        });
 -        return {
 -            domains: domains,
 -            contexts: contexts,
 -            groupbys: groupbys,
 -            errors: errors
 -        };
 -    }, 
 -    /**
 -     * Performs the search view collection of widget data.
 -     *
 -     * If the collection went well (all fields are valid), then triggers
 -     * :js:func:`instance.web.SearchView.on_search`.
 -     *
 -     * If at least one field failed its validation, triggers
 -     * :js:func:`instance.web.SearchView.on_invalid` instead.
 -     *
 -     * @param [_query]
 -     * @param {Object} [options]
 -     */
 -    do_search: function (_query, options) {
 -        if (options && options.preventSearch) {
 -            return;
 -        }
 -        var search = this.build_search_data();
 -        if (!_.isEmpty(search.errors)) {
 -            this.on_invalid(search.errors);
 -            return;
 -        }
 -        this.trigger('search_data', search.domains, search.contexts, search.groupbys);
 -    },
 -    /**
 -     * Triggered after the SearchView has collected all relevant domains and
 -     * contexts.
 -     *
 -     * It is provided with an Array of domains and an Array of contexts, which
 -     * may or may not be evaluated (each item can be either a valid domain or
 -     * context, or a string to evaluate in order in the sequence)
 -     *
 -     * It is also passed an array of contexts used for group_by (they are in
 -     * the correct order for group_by evaluation, which contexts may not be)
 -     *
 -     * @event
 -     * @param {Array} domains an array of literal domains or domain references
 -     * @param {Array} contexts an array of literal contexts or context refs
 -     * @param {Array} groupbys ordered contexts which may or may not have group_by keys
 -     */
 -    /**
 -     * Triggered after a validation error in the SearchView fields.
 -     *
 -     * Error objects have three keys:
 -     * * ``field`` is the name of the invalid field
 -     * * ``value`` is the invalid value
 -     * * ``message`` is the (in)validation message provided by the field
 -     *
 -     * @event
 -     * @param {Array} errors a never-empty array of error objects
 -     */
 -    on_invalid: function (errors) {
 -        this.do_notify(_t("Invalid Search"), _t("triggered from search view"));
 -        this.trigger('invalid_search', errors);
 -    },
 -
 -    // The method appendTo is overwrited to be able to insert the drawer anywhere
 -    appendTo: function ($searchview_parent, $searchview_drawer_node) {
 -        var $searchview_drawer_node = $searchview_drawer_node || $searchview_parent;
 -
 -        return $.when(
 -            this._super($searchview_parent),
 -            this.drawer.appendTo($searchview_drawer_node)
 -        );
 -    },
 -
 -    destroy: function () {
 -        this.drawer.destroy();
 -        this.getParent().destroy.call(this);
 -    }
 -});
 -
 -instance.web.SearchViewDrawer = instance.web.Widget.extend({
 -    template: "SearchViewDrawer",
 -
 -    init: function(parent, searchview) {
 -        this._super(parent);
 -        this.searchview = searchview;
 -        this.searchview.set_drawer(this);
 -        this.ready = searchview.drawer_ready;
 -        this.controls = [];
 -        this.inputs = [];
 -    },
 -
 -    toggle: function (visibility) {
 -        this.$el.toggle(visibility);
 -        var $view_manager_body = this.$el.closest('.oe_view_manager_body');
 -        if ($view_manager_body.length) {
 -            $view_manager_body.scrollTop(0);
 -        }
 -    },
 -
 -    start: function() {
 -        var self = this;
 -        if (this.searchview.headless) return $.when(this._super(), this.searchview.ready);
 -        var filters_ready = this.searchview.fields_view_get
 -                                .then(this.proxy('prepare_filters'));
 -        return $.when(this._super(), filters_ready).then(function () {
 -            var defaults = arguments[1][0];
 -            self.ready.resolve.apply(null, defaults);
 -        });
 -    },
 -    prepare_filters: function (data) {
 -        this.make_widgets(
 -            data['arch'].children,
 -            data.fields);
 -
 -        this.add_common_inputs();
 -
 -        // build drawer
 -        var in_drawer = this.select_for_drawer();
 -
 -        var $first_col = this.$(".col-md-7"),
 -            $snd_col = this.$(".col-md-5");
 -
 -        var add_custom_filters = in_drawer[0].appendTo($first_col),
 -            add_filters = in_drawer[1].appendTo($first_col),
 -            add_rest = $.when.apply(null, _(in_drawer.slice(2)).invoke('appendTo', $snd_col)),
 -            defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
 -                'facet_for_defaults', this.searchview.defaults));
 -
 -        return $.when(defaults_fetched, add_custom_filters, add_filters, add_rest);
 -    },
 -    /**
 -     * Sets up thingie where all the mess is put?
 -     */
 -    select_for_drawer: function () {
 -        return _(this.inputs).filter(function (input) {
 -            return input.in_drawer();
 -        });
 -    },
 -
 -    /**
 -     * Builds a list of widget rows (each row is an array of widgets)
 -     *
 -     * @param {Array} items a list of nodes to convert to widgets
 -     * @param {Object} fields a mapping of field names to (ORM) field attributes
 -     * @param {Object} [group] group to put the new controls in
 -     */
 -    make_widgets: function (items, fields, group) {
 -        if (!group) {
 -            group = new instance.web.search.Group(
 -                this, 'q', {attrs: {string: _t("Filters")}});
 -        }
 -        var self = this;
 -        var filters = [];
 -        _.each(items, function (item) {
 -            if (filters.length && item.tag !== 'filter') {
 -                group.push(new instance.web.search.FilterGroup(filters, group));
 -                filters = [];
 -            }
 -
 -            switch (item.tag) {
 -            case 'separator': case 'newline':
 -                break;
 -            case 'filter':
 -                filters.push(new instance.web.search.Filter(item, group));
 -                break;
 -            case 'group':
 -                self.add_separator();
 -                self.make_widgets(item.children, fields,
 -                    new instance.web.search.Group(group, 'w', item));
 -                self.add_separator();
 -                break;
 -            case 'field':
 -                var field = this.make_field(
 -                    item, fields[item['attrs'].name], group);
 -                group.push(field);
 -                // filters
 -                self.make_widgets(item.children, fields, group);
 -                break;
 -            }
 -        }, this);
 -
 -        if (filters.length) {
 -            group.push(new instance.web.search.FilterGroup(filters, this));
 -        }
      },
 -
 -    add_separator: function () {
 -        if (!(_.last(this.inputs) instanceof instance.web.search.Separator))
 -            new instance.web.search.Separator(this);
 +    childBlurred: function () {
 +        this.$el.val('').removeClass('active').trigger('blur');
 +        this.autocomplete.close();
      },
      /**
 -     * Creates a field for the provided field descriptor item (which comes
 -     * from fields_view_get)
 -     *
 -     * @param {Object} item fields_view_get node for the field
 -     * @param {Object} field fields_get result for the field
 -     * @param {Object} [parent]
 -     * @returns instance.web.search.Field
 +     * Call the renderFacets method with the correct arguments.
 +     * This is due to the fact that change events are called with two arguments
 +     * (model, options) while add, reset and remove events are called with
 +     * (collection, model, options) as arguments
       */
 -    make_field: function (item, field, parent) {
 -        // M2O combined with selection widget is pointless and broken in search views,
 -        // but has been used in the past for unsupported hacks -> ignore it
 -        if (field.type === "many2one" && item.attrs.widget === "selection"){
 -            item.attrs.widget = undefined;
 -        }
 -        var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
 -        if(obj) {
 -            return new (obj) (item, field, parent || this);
 -        } else {
 -            console.group('Unknown field type ' + field.type);
 -            console.error('View node', item);
 -            console.info('View field', field);
 -            console.info('In view', this);
 -            console.groupEnd();
 -            return null;
 -        }
 -    },
 -
 -    add_common_inputs: function() {
 -        // add custom filters to this.inputs
 -        this.custom_filters = new instance.web.search.CustomFilters(this);
 -        // add Filters to this.inputs, need view.controls filled
 -        (new instance.web.search.Filters(this));
 -        (new instance.web.search.SaveFilter(this, this.custom_filters));
 -        // add Advanced to this.inputs
 -        (new instance.web.search.Advanced(this));
 +    renderChangedFacets: function (model, options) {
 +        this.renderFacets(undefined, model, options);
      },
 -
  });
  
  /**
@@@ -719,17 -930,91 +720,17 @@@ instance.web.search.fields = new instan
      'many2many': 'instance.web.search.CharField',
      'one2many': 'instance.web.search.CharField'
  });
 -instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web.search.Invalid# */{
 -    /**
 -     * Exception thrown by search widgets when they hold invalid values,
 -     * which they can not return when asked.
 -     *
 -     * @constructs instance.web.search.Invalid
 -     * @extends instance.web.Class
 -     *
 -     * @param field the name of the field holding an invalid value
 -     * @param value the invalid value
 -     * @param message validation failure message
 -     */
 -    init: function (field, value, message) {
 -        this.field = field;
 -        this.value = value;
 -        this.message = message;
 -    },
 -    toString: function () {
 -        return _.str.sprintf(
 -            _t("Incorrect value for field %(fieldname)s: [%(value)s] is %(message)s"),
 -            {fieldname: this.field, value: this.value, message: this.message}
 -        );
 -    }
 -});
 -instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{
 -    template: null,
 -    /**
 -     * Root class of all search widgets
 -     *
 -     * @constructs instance.web.search.Widget
 -     * @extends instance.web.Widget
 -     *
 -     * @param parent parent of this widget
 -     */
 -    init: function (parent) {
 -        this._super(parent);
 -        var ancestor = parent;
 -        do {
 -            this.drawer = ancestor;
 -        } while (!(ancestor instanceof instance.web.SearchViewDrawer)
 -               && (ancestor = (ancestor.getParent && ancestor.getParent())));
 -        this.view = this.drawer.searchview || this.drawer;
 -    }
 -});
 -
 -instance.web.search.add_expand_listener = function($root) {
 -    $root.find('a.searchview_group_string').click(function (e) {
 -        $root.toggleClass('folded expanded');
 -        e.stopPropagation();
 -        e.preventDefault();
 -    });
 -};
 -instance.web.search.Group = instance.web.search.Widget.extend({
 -    init: function (parent, icon, node) {
 -        this._super(parent);
 -        var attrs = node.attrs;
 -        this.modifiers = attrs.modifiers =
 -            attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
 -        this.attrs = attrs;
 -        this.icon = icon;
 -        this.name = attrs.string;
 -        this.children = [];
  
 -        this.drawer.controls.push(this);
 -    },
 -    push: function (input) {
 -        this.children.push(input);
 -    },
 -    visible: function () {
 -        return !this.modifiers.invisible;
 -    },
 -});
 -
 -instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{
 -    _in_drawer: false,
 +instance.web.search.Input = instance.web.Widget.extend( /** @lends instance.web.search.Input# */{
      /**
       * @constructs instance.web.search.Input
 -     * @extends instance.web.search.Widget
 +     * @extends instance.web.Widget
       *
       * @param parent
       */
      init: function (parent) {
          this._super(parent);
          this.load_attrs({});
 -        this.drawer.inputs.push(this);
      },
      /**
       * Fetch auto-completion values for the widget.
          }
          return this.facet_for(defaults[this.attrs.name]);
      },
 -    in_drawer: function () {
 -        return !!this._in_drawer;
 -    },
      get_context: function () {
          throw new Error(
              "get_context not implemented for widget " + this.attrs.type);
       * @returns {Boolean}
       */
      visible: function () {
 -        if (this.attrs.modifiers.invisible) {
 -            return false;
 -        }
 -        var parent = this;
 -        while ((parent = parent.getParent()) &&
 -               (   (parent instanceof instance.web.search.Group)
 -                || (parent instanceof instance.web.search.Input))) {
 -            if (!parent.visible()) {
 -                return false;
 -            }
 -        }
 -        return true;
 +        return !this.attrs.modifiers.invisible;
      },
  });
  instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{
      template: 'SearchView.filters',
 -    icon: 'q',
 +    icon: "fa-filter",
      completion_label: _lt("Filter on: %s"),
      /**
       * Inclusive group of filters, creates a continuous "button" with clickable
          }
          this._super(parent);
          this.filters = filters;
 -        this.view.query.on('add remove change reset', this.proxy('search_change'));
 +        this.searchview = parent;
 +        this.searchview.query.on('add remove change reset', this.proxy('search_change'));
      },
      start: function () {
 -        this.$el.on('click', 'li', this.proxy('toggle_filter'));
 +        this.$el.on('click', 'a', this.proxy('toggle_filter'));
          return $.when(null);
      },
      /**
       */
      search_change: function () {
          var self = this;
 -        var $filters = this.$('> li').removeClass('badge');
 -        var facet = this.view.query.find(_.bind(this.match_facet, this));
 +        var $filters = this.$el.removeClass('selected');
 +        var facet = this.searchview.query.find(_.bind(this.match_facet, this));
          if (!facet) { return; }
          facet.values.each(function (v) {
              var i = _(self.filters).indexOf(v.get('value'));
              if (i === -1) { return; }
              $filters.filter(function () {
                  return Number($(this).data('index')) === i;
 -            }).addClass('badge');
 +            }).addClass('selected');
          });
      },
      /**
          });
      },
      toggle_filter: function (e) {
 -        this.toggle(this.filters[Number($(e.target).data('index'))]);
 +        e.stopPropagation();
 +        this.toggle(this.filters[Number($(e.target).parent().data('index'))]);
 +    },
 +    toggle: function (filter, options) {
 +        this.searchview.query.toggle(this.make_facet([this.make_value(filter)]), options);
      },
 -    toggle: function (filter) {
 -        this.view.query.toggle(this.make_facet([this.make_value(filter)]));
 +    is_visible: function () {
 +        return _.some(this.filters, function (filter) {
 +            return !filter.attrs.invisible;
 +        });
      },
      complete: function (item) {
          var self = this;
      }
  });
  instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
 -    icon: 'w',
 +    icon: 'fa-bars',
      completion_label: _lt("Group by: %s"),
      init: function (filters, parent) {
          this._super(filters, parent);
 +        this.searchview = parent;
          // Not flanders: facet unicity is handled through the
          // (category, field) pair of facet attributes. This is all well and
          // good for regular filter groups where a group matches a facet, but for
          // view which proxies to the first GroupbyGroup, so it can be used
          // for every GroupbyGroup and still provides the various methods needed
          // by the search view. Use weirdo name to avoid risks of conflicts
 -        if (!this.view._s_groupby) {
 -            this.view._s_groupby = {
 +        if (!this.searchview._s_groupby) {
 +            this.searchview._s_groupby = {
                  help: "See GroupbyGroup#init",
                  get_context: this.proxy('get_context'),
                  get_domain: this.proxy('get_domain'),
          }
      },
      match_facet: function (facet) {
 -        return facet.get('field') === this.view._s_groupby;
 +        return facet.get('field') === this.searchview._s_groupby;
      },
      make_facet: function (values) {
          return {
              category: _t("GroupBy"),
              icon: this.icon,
              values: values,
 -            field: this.view._s_groupby
 +            field: this.searchview._s_groupby
          };
      }
  });
@@@ -1023,6 -1314,14 +1024,6 @@@ instance.web.search.Filter = instance.w
      get_domain: function () { },
  });
  
 -instance.web.search.Separator = instance.web.search.Input.extend({
 -    _in_drawer: false,
 -
 -    complete: function () {
 -        return {is_separator: true};
 -    }
 -});
 -
  instance.web.search.Field = instance.web.search.Input.extend( /** @lends instance.web.search.Field# */ {
      template: 'SearchView.field',
      default_operator: '=',
@@@ -1197,7 -1496,7 +1198,7 @@@ instance.web.search.FloatField = instan
  function facet_from(field, pair) {
      return {
          field: field,
 -        category: field['attrs'].string,
 +        category: field.attrs.string,
          values: [{label: pair[1], value: pair[0]}]
      };
  }
@@@ -1274,9 -1573,20 +1275,9 @@@ instance.web.search.DateField = instanc
          return instance.web.date_to_str(facetValue.get('value'));
      },
      complete: function (needle) {
 -        var d;
 -        try {
 -            var t = (this.attrs && this.attrs.type === 'datetime') ? 'datetime' : 'date';
 -            var v = instance.web.parse_value(needle, {'widget': t});
 -            if (t === 'datetime'){
 -                d = instance.web.str_to_datetime(v);
 -            }
 -            else{
 -                d = instance.web.str_to_date(v);
 -            }
 -        } catch (e) {
 -            // pass
 -        }
 -        if (!d) { return $.when(null); }
 +        var m = moment(needle);
 +        if (!m.isValid()) { return $.when(null); }
 +        var d = m.toDate();
          var date_string = instance.web.format_value(d, this.attrs);
          var label = _.str.sprintf(_.str.escapeHTML(
              _t("Search %(field)s at: %(value)s")), {
@@@ -1313,7 -1623,6 +1314,7 @@@ instance.web.search.ManyToOneField = in
      init: function (view_section, field, parent) {
          this._super(view_section, field, parent);
          this.model = new instance.web.Model(this.attrs.relation);
 +        this.searchview = parent;
      },
  
      complete: function (value) {
          var self = this;
          // FIXME: "concurrent" searches (multiple requests, mis-ordered responses)
          var context = instance.web.pyeval.eval(
 -            'contexts', [this.view.dataset.get_context()]);
 +            'contexts', [this.searchview.dataset.get_context()]);
          return this.model.call('name_search', [], {
              name: needle,
              args: (typeof this.attrs.domain === 'string') ? [] : this.attrs.domain,
          var values = facet.values;
          if (_.isEmpty(this.attrs.context) && values.length === 1) {
              var c = {};
-             c['default_' + this.attrs.name] = values.at(0).get('value');
+             var v = values.at(0);
+             if (v.get('operator') !== 'ilike') {
+                 c['default_' + this.attrs.name] = v.get('value');
+             }
              return c;
          }
          return this._super(facet);
      }
  });
  
 -instance.web.search.CustomFilters = instance.web.search.Input.extend({
 -    template: 'SearchView.Custom',
 -    _in_drawer: true,
 -    init: function () {
 -        this.is_ready = $.Deferred();
 -        this._super.apply(this,arguments);
 +instance.web.search.FilterMenu = instance.web.Widget.extend({
 +    template: 'SearchView.FilterMenu',
 +    events: {
 +        'click .oe-add-filter': function () {
 +            this.toggle_custom_filter_menu();
 +        },
 +        'click li': function (event) {event.stopImmediatePropagation();},
 +        'hidden.bs.dropdown': function () {
 +            this.toggle_custom_filter_menu(false);
 +        },
 +        'click .oe-add-condition': 'append_proposition',
 +        'click .oe-apply-filter': 'commit_search',
 +    },
 +    init: function (parent, filters, fields_def) {
 +        var self = this;
 +        this._super(parent);
 +        this.filters = filters || [];
 +        this.searchview = parent;
 +        this.propositions = [];
 +        this.fields_def = fields_def.then(function (data) {
 +            var fields = {
 +                id: { string: 'ID', type: 'id', searchable: true }
 +            };
 +            _.each(data, function(field_def, field_name) {
 +                if (field_def.selectable !== false && field_name !== 'id') {
 +                    fields[field_name] = field_def;
 +                }
 +            });
 +            return fields;
 +        });
      },
      start: function () {
          var self = this;
 +        this.$menu = this.$('.filters-menu');
 +        this.$add_filter = this.$('.oe-add-filter');
 +        this.$apply_filter = this.$('.oe-apply-filter');
 +        this.$add_filter_menu = this.$('.oe-add-filter-menu');
 +        _.each(this.filters, function (group) {
 +            if (group.is_visible()) {
 +                group.insertBefore(self.$add_filter);
 +                $('<li class="divider">').insertBefore(self.$add_filter);
 +            }
 +        });
 +        this.append_proposition().then(function (prop) {
 +            prop.$el.hide();
 +        });
 +    },
 +    update_max_height: function () {
 +        var max_height = $(window).height() - this.$menu[0].getBoundingClientRect().top - 10;
 +        this.$menu.css('max-height', max_height);
 +    },
 +    toggle_custom_filter_menu: function (is_open) {
 +        this.$add_filter
 +            .toggleClass('closed-menu', !is_open)
 +            .toggleClass('open-menu', is_open);
 +        this.$add_filter_menu.toggle(is_open);
 +        if (this.$add_filter.hasClass('closed-menu') && (!this.propositions.length)) {
 +            this.append_proposition();
 +        }
 +        this.$('.oe-filter-condition').toggle(is_open);
 +        this.update_max_height();
 +    },
 +    append_proposition: function () {
 +        var self = this;
 +        return this.fields_def.then(function (fields) {
 +            var prop = new instance.web.search.ExtendedSearchProposition(self, fields);
 +            self.propositions.push(prop);
 +            prop.insertBefore(self.$add_filter_menu);
 +            self.$apply_filter.prop('disabled', false);
 +            self.update_max_height();
 +            return prop;
 +        });
 +    },
 +    remove_proposition: function (prop) {
 +        this.propositions = _.without(this.propositions, prop);
 +        if (!this.propositions.length) {
 +            this.$apply_filter.prop('disabled', true);
 +        }
 +        prop.destroy();
 +    },
 +    commit_search: function () {
 +        var filters = _.invoke(this.propositions, 'get_filter'),
 +            filters_widgets = _.map(filters, function (filter) {
 +                return new my.Filter(filter, this);
 +            }),
 +            filter_group = new my.FilterGroup(filters_widgets, this.searchview),
 +            facets = filters_widgets.map(function (filter) {
 +                return filter_group.make_facet([filter_group.make_value(filter)]);
 +            });
 +        filter_group.insertBefore(this.$add_filter);
 +        $('<li class="divider">').insertBefore(this.$add_filter);
 +        this.searchview.query.add(facets, {silent: true});
 +        this.searchview.query.trigger('reset');
 +
 +        _.invoke(this.propositions, 'destroy');
 +        this.propositions = [];
 +        this.append_proposition();
 +        this.toggle_custom_filter_menu(false);
 +    },
 +});
 +
 +instance.web.search.GroupByMenu = instance.web.Widget.extend({
 +    template: 'SearchView.GroupByMenu',
 +    events: {
 +        'click li': function (event) {
 +            event.stopImmediatePropagation();
 +        },
 +        'hidden.bs.dropdown': function () {
 +            this.toggle_add_menu(false);
 +        },
 +        'click .add-custom-group a': function () {
 +            this.toggle_add_menu();
 +        },
 +    },
 +    init: function (parent, groups, fields_def) {
 +        this._super(parent);
 +        this.groups = groups || [];
 +        this.groupable_fields = {};
 +        this.searchview = parent;
 +        this.fields_def = fields_def.then(this.proxy('get_groupable_fields'));
 +    },
 +    start: function () {
 +        var self = this;
 +        this.$menu = this.$('.group-by-menu');
 +        var divider = this.$menu.find('.divider');
 +        _.invoke(this.groups, 'insertBefore', divider);
 +        if (this.groups.length) {
 +            divider.show();
 +        }
 +        this.$add_group = this.$menu.find('.add-custom-group');
 +        this.fields_def.then(function () {
 +            self.$menu.append(QWeb.render('GroupByMenuSelector', self));
 +            self.$add_group_menu = self.$('.oe-add-group');
 +            self.$group_selector = self.$('.oe-group-selector');
 +            self.$('.oe-select-group').click(function (event) {
 +                self.toggle_add_menu(false);
 +                var field = self.$group_selector.find(':selected').data('name');
 +                self.add_groupby_to_menu(field);
 +            });            
 +        });
 +    },
 +    get_groupable_fields: function (fields) {
 +        var self = this,
 +            groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime'];
 +
 +        _.each(fields, function (field, name) {
 +            if (field.store && _.contains(groupable_types, field.type)) {
 +                self.groupable_fields[name] = field;
 +            }
 +        });
 +    },
 +    toggle_add_menu: function (is_open) {
 +        this.$add_group
 +            .toggleClass('closed-menu', !is_open)
 +            .toggleClass('open-menu', is_open);
 +        this.$add_group_menu.toggle(is_open);
 +        if (this.$add_group.hasClass('open-menu')) {
 +            this.$group_selector.focus();
 +        }
 +    },
 +    add_groupby_to_menu: function (field_name) {
 +        var filter = new my.Filter({attrs:{
 +            context:"{'group_by':'" + field_name + "''}",
 +            name: this.groupable_fields[field_name].string,
 +        }}, this.searchview);
 +        var group = new my.FilterGroup([filter], this.searchview),
 +            divider = this.$('.divider').show();
 +        group.insertBefore(divider);
 +        group.toggle(filter);
 +    },
 +});
 +
 +instance.web.search.FavoriteMenu = instance.web.Widget.extend({
 +    template: 'SearchView.FavoriteMenu',
 +    events: {
 +        'click li': function (event) {
 +            event.stopImmediatePropagation();
 +        },
 +        'click .oe-save-search a': function () {
 +            this.toggle_save_menu();
 +        },
 +        'click .oe-save-name button': 'save_favorite',
 +        'hidden.bs.dropdown': function () {
 +            this.close_menus();
 +        },
 +    },
 +    init: function (parent, query, target_model, action_id) {
 +        this._super.apply(this,arguments);
 +        this.searchview = parent;
 +        this.query = query;
 +        this.target_model = target_model;
          this.model = new instance.web.Model('ir.filters');
          this.filters = {};
          this.$filters = {};
 -        this.view.query
 +        this.action_id = action_id;
 +    },
 +    start: function () {
 +        var self = this;
 +        this.$save_search = this.$('.oe-save-search');
 +        this.$save_name = this.$('.oe-save-name');
 +        this.$inputs = this.$save_name.find('input');
 +        this.$divider = this.$('.divider');
 +        this.$inputs.eq(0).val(this.searchview.getParent().title);
 +        var $shared_filter = this.$inputs.eq(1),
 +            $default_filter = this.$inputs.eq(2);
 +        $shared_filter.click(function () {$default_filter.prop('checked', false)});
 +        $default_filter.click(function () {$shared_filter.prop('checked', false)});
 +
 +        this.query
              .on('remove', function (facet) {
 -                if (!facet.get('is_custom_filter')) {
 -                    return;
 +                if (facet.get('is_custom_filter')) {
 +                    self.clear_selection();
                  }
 -                self.clear_selection();
              })
              .on('reset', this.proxy('clear_selection'));
 -        return this.model.call('get_filters', [this.view.model, this.get_action_id()])
 -            .then(this.proxy('set_filters'))
 -            .done(function () { self.is_ready.resolve(); })
 -            .fail(function () { self.is_ready.reject.apply(self.is_ready, arguments); });
 +        if (!this.action_id) return $.when();
 +        return this.model.call('get_filters', [this.target_model, this.action_id])
 +            .done(this.proxy('prepare_dropdown_menu'));
 +    },
 +    prepare_dropdown_menu: function (filters) {
 +        filters.map(this.append_filter.bind(this));
 +    },
 +    toggle_save_menu: function (is_open) {
 +        this.$save_search
 +            .toggleClass('closed-menu', !is_open)
 +            .toggleClass('open-menu', is_open);
 +        this.$save_name.toggle(is_open);
 +        if (this.$save_search.hasClass('open-menu')) {
 +            this.$save_name.find('input').first().focus();
 +        }
      },
 -    get_action_id: function(){
 -        var action = instance.client.action_manager.inner_action;
 -        if (action) return action.id;
 +    close_menus: function () {
 +        this.toggle_save_menu(false);
      },
 -    /**
 -     * Special implementation delaying defaults until CustomFilters is loaded
 -     */
 -    facet_for_defaults: function () {
 -        return this.is_ready;
 +    save_favorite: function () {
 +        var self = this,
 +            filter_name = this.$inputs[0].value,
 +            default_filter = this.$inputs[1].checked,
 +            shared_filter = this.$inputs[2].checked;
 +        if (!filter_name.length){
 +            this.do_warn(_t("Error"), _t("Filter name is required."));
 +            this.$inputs.first().focus();
 +            return;
 +        }
 +        var search = this.searchview.build_search_data(),
 +            results = instance.web.pyeval.sync_eval_domains_and_contexts({
 +                domains: search.domains,
 +                contexts: search.contexts,
 +                group_by_seq: search.groupbys || [],
 +            });
 +        if (!_.isEmpty(results.group_by)) {
 +            results.context.group_by = results.group_by;
 +        }
 +        // Don't save user_context keys in the custom filter, otherwise end
 +        // up with e.g. wrong uid or lang stored *and used in subsequent
 +        // reqs*
 +        var ctx = results.context;
 +        _(_.keys(instance.session.user_context)).each(function (key) {
 +            delete ctx[key];
 +        });
 +        var filter = {
 +            name: filter_name,
 +            user_id: shared_filter ? false : instance.session.uid,
 +            model_id: this.searchview.dataset.model,
 +            context: results.context,
 +            domain: results.domain,
 +            is_default: default_filter,
 +            action_id: this.action_id,
 +        };
 +        return this.model.call('create_or_replace', [filter]).done(function (id) {
 +            filter.id = id;
 +            self.toggle_save_menu(false);
 +            self.$save_name.find('input').val('').prop('checked', false);
 +            self.append_filter(filter);
 +            self.toggle_filter(filter, true);
 +        });
 +    },
 +    get_default_filter: function () {
 +        var personal_filter = _.find(this.filters, function (filter) {
 +            return filter.user_id && filter.is_default;
 +        });
 +        if (personal_filter) {
 +            return personal_filter;
 +        }
 +        return _.find(this.filters, function (filter) {
 +            return !filter.user_id && filter.is_default;
 +        });
      },
      /**
       * Generates a mapping key (in the filters and $filter mappings) for the
       */
      key_for: function (filter) {
          var user_id = filter.user_id,
 -            action_id = filter.action_id;
 -        var uid = (user_id instanceof Array) ? user_id[0] : user_id;
 -        var act_id = (action_id instanceof Array) ? action_id[0] : action_id;
 +            action_id = filter.action_id,
 +            uid = (user_id instanceof Array) ? user_id[0] : user_id,
 +            act_id = (action_id instanceof Array) ? action_id[0] : action_id;
          return _.str.sprintf('(%s)(%s)%s', uid, act_id, filter.name);
      },
      /**
      facet_for: function (filter) {
          return {
              category: _t("Custom Filter"),
 -            icon: 'M',
 +            icon: 'fa-star',
              field: {
                  get_context: function () { return filter.context; },
                  get_groupby: function () { return [filter.context]; },
                  get_domain: function () { return filter.domain; }
              },
 -            _id: filter['id'],
 +            _id: filter.id,
              is_custom_filter: true,
              values: [{label: filter.name, value: null}]
          };
      },
      clear_selection: function () {
 -        this.$('span.badge').removeClass('badge');
 +        this.$('li.selected').removeClass('selected');
      },
      append_filter: function (filter) {
 -        var self = this;
 -        var key = this.key_for(filter);
 -        var warning = _t("This filter is global and will be removed for everybody if you continue.");
 +        var self = this,
 +            key = this.key_for(filter),
 +            $filter;
  
 -        var $filter;
 +        this.$divider.show();
          if (key in this.$filters) {
              $filter = this.$filters[key];
          } else {
 -            var id = filter.id;
              this.filters[key] = filter;
              $filter = $('<li></li>')
 -                .appendTo(this.$('.oe_searchview_custom_list'))
 +                .insertBefore(this.$divider)
                  .toggleClass('oe_searchview_custom_default', filter.is_default)
 -                .append(this.$filters[key] = $('<span>').text(filter.name));
 +                .append($('<a>').text(filter.name));
  
 +            this.$filters[key] = $filter;
              this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private'
                                           : 'oe_searchview_custom_public')
 -
 -            $('<a class="oe_searchview_custom_delete">x</a>')
 -                .click(function (e) {
 -                    e.stopPropagation();
 -                    if (!(filter.user_id || confirm(warning))) {
 -                        return;
 -                    }
 -                    self.model.call('unlink', [id]).done(function () {
 -                        $filter.remove();
 -                        delete self.$filters[key];
 -                        delete self.filters[key];
 -                        if (_.isEmpty(self.filters)) {
 -                            self.hide();
 -                        }
 -                    });
 +            $('<span>')
 +                .addClass('fa fa-trash-o remove-filter')
 +                .click(function (event) {
 +                    event.stopImmediatePropagation(); 
 +                    self.remove_filter(filter, $filter, key);
                  })
                  .appendTo($filter);
          }
          this.$filters[key].unbind('click').click(function () {
              self.toggle_filter(filter);
          });
 -        this.show();
      },
      toggle_filter: function (filter, preventSearch) {
 -        var current = this.view.query.find(function (facet) {
 +        var current = this.query.find(function (facet) {
              return facet.get('_id') === filter.id;
          });
          if (current) {
 -            this.view.query.remove(current);
 -            this.$filters[this.key_for(filter)].removeClass('badge');
 +            this.query.remove(current);
 +            this.$filters[this.key_for(filter)].removeClass('selected');
              return;
          }
 -        this.view.query.reset([this.facet_for(filter)], {
 +        this.query.reset([this.facet_for(filter)], {
              preventSearch: preventSearch || false});
 -        this.$filters[this.key_for(filter)].addClass('badge');
 -    },
 -    set_filters: function (filters) {
 -        _(filters).map(_.bind(this.append_filter, this));
 -        if (!filters.length) {
 -            this.hide();
 -        }
 -    },
 -    hide: function () {
 -        this.$el.hide();
 -    },
 -    show: function () {
 -        this.$el.show();
 -    },
 -});
 -
 -instance.web.search.SaveFilter = instance.web.search.Input.extend({
 -    template: 'SearchView.SaveFilter',
 -    _in_drawer: true,
 -    init: function (parent, custom_filters) {
 -        this._super(parent);
 -        this.custom_filters = custom_filters;
 -    },
 -    start: function () {
 -        var self = this;
 -        this.model = new instance.web.Model('ir.filters');
 -        this.$el.on('submit', 'form', this.proxy('save_current'));
 -        this.$el.on('click', 'input[type=checkbox]', function() {
 -            $(this).siblings('input[type=checkbox]').prop('checked', false);
 -        });
 -        this.$el.on('click', 'h4', function () {
 -            self.$el.toggleClass('oe_opened');
 -        });
 +        this.$filters[this.key_for(filter)].addClass('selected');
      },
 -    save_current: function () {
 +    remove_filter: function (filter, $filter, key) {
          var self = this;
 -        var $name = this.$('input:first');
 -        var private_filter = !this.$('#oe_searchview_custom_public').prop('checked');
 -        var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
 -        if (_.isEmpty($name.val())){
 -            this.do_warn(_t("Error"), _t("Filter name is required."));
 -            return false;
 +        var global_warning = _t("This filter is global and will be removed for everybody if you continue."),
 +            warning = _t("Are you sure that you want to remove this filter?");
 +        if (!confirm(filter.user_id ? warning : global_warning)) {
 +            return;
          }
 -        var search = this.view.build_search_data();
 -        instance.web.pyeval.eval_domains_and_contexts({
 -            domains: search.domains,
 -            contexts: search.contexts,
 -            group_by_seq: search.groupbys || []
 -        }).done(function (results) {
 -            if (!_.isEmpty(results.group_by)) {
 -                results.context.group_by = results.group_by;
 +        this.model.call('unlink', [filter.id]).done(function () {
 +            $filter.remove();
 +            delete self.$filters[key];
 +            delete self.filters[key];
 +            if (_.isEmpty(self.filters)) {
 +                self.$divider.hide();
              }
 -            // Don't save user_context keys in the custom filter, otherwise end
 -            // up with e.g. wrong uid or lang stored *and used in subsequent
 -            // reqs*
 -            var ctx = results.context;
 -            _(_.keys(instance.session.user_context)).each(function (key) {
 -                delete ctx[key];
 -            });
 -            var filter = {
 -                name: $name.val(),
 -                user_id: private_filter ? instance.session.uid : false,
 -                model_id: self.view.model,
 -                context: results.context,
 -                domain: results.domain,
 -                is_default: set_as_default,
 -                action_id: self.custom_filters.get_action_id()
 -            };
 -            // FIXME: current context?
 -            return self.model.call('create_or_replace', [filter]).done(function (id) {
 -                filter.id = id;
 -                if (self.custom_filters) {
 -                    self.custom_filters.append_filter(filter);
 -                }
 -                self.$el
 -                    .removeClass('oe_opened')
 -                    .find('form')[0].reset();
 -            });
 -        });
 -        return false;
 +        });        
      },
  });
  
 -instance.web.search.Filters = instance.web.search.Input.extend({
 -    template: 'SearchView.Filters',
 -    _in_drawer: true,
 -    start: function () {
 -        var self = this;
 -        var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };
 -        var visible_filters = _(this.drawer.controls).chain().reject(function (group) {
 -            return _(_(group.children).filter(is_group)).isEmpty()
 -                || group.modifiers.invisible;
 -        });
 -
 -        var groups = visible_filters.map(function (group) {
 -                var filters = _(group.children).filter(is_group);
 -                return {
 -                    name: _.str.sprintf("<span class='oe_i'>%s</span> %s",
 -                            group.icon, group.name),
 -                    filters: filters,
 -                    length: _(filters).chain().map(function (i) {
 -                        return i.filters.length; }).sum().value()
 -                };
 -            }).value();
 -
 -        var $dl = $('<dl class="dl-horizontal">').appendTo(this.$el);
 -
 -        var rendered_lines = _.map(groups, function (group) {
 -            $('<dt>').html(group.name).appendTo($dl);
 -            var $dd = $('<dd>').appendTo($dl);
 -            return $.when.apply(null, _(group.filters).invoke('appendTo', $dd));
 -        });
 -
 -        return $.when.apply(this, rendered_lines);
 -    },
 -});
 -
 -instance.web.search.Advanced = instance.web.search.Input.extend({
 -    template: 'SearchView.advanced',
 -    _in_drawer: true,
 -    start: function () {
 -        var self = this;
 -        this.$el
 -            .on('keypress keydown keyup', function (e) { e.stopPropagation(); })
 -            .on('click', 'h4', function () {
 -                self.$el.toggleClass('oe_opened');
 -            }).on('click', 'button.oe_add_condition', function () {
 -                self.append_proposition();
 -            }).on('submit', 'form', function (e) {
 -                e.preventDefault();
 -                self.commit_search();
 -            });
 -        return $.when(
 -            this._super(),
 -            new instance.web.Model(this.view.model).call('fields_get', {
 -                    context: this.view.dataset.context
 -                }).done(function(data) {
 -                    self.fields = {
 -                        id: { string: 'ID', type: 'id', searchable: true }
 -                    };
 -                    _.each(data, function(field_def, field_name) {
 -                        if (field_def.selectable !== false && field_name != 'id') {
 -                            self.fields[field_name] = field_def;
 -                        }
 -                    });
 -        })).done(function () {
 -            self.append_proposition();
 -        });
 -    },
 -    append_proposition: function () {
 -        var self = this;
 -        return (new instance.web.search.ExtendedSearchProposition(this, this.fields))
 -            .appendTo(this.$('ul')).done(function () {
 -                self.$('button.oe_apply').prop('disabled', false);
 -            });
 -    },
 -    remove_proposition: function (prop) {
 -        // removing last proposition, disable apply button
 -        if (this.getChildren().length <= 1) {
 -            this.$('button.oe_apply').prop('disabled', true);
 -        }
 -        prop.destroy();
 -    },
 -    commit_search: function () {
 -        // Get domain sections from all propositions
 -        var children = this.getChildren();
 -        var propositions = _.invoke(children, 'get_proposition');
 -        var domain = _(propositions).pluck('value');
 -        for (var i = domain.length; --i;) {
 -            domain.unshift('|');
 -        }
 -
 -        this.view.query.add({
 -            category: _t("Advanced"),
 -            values: propositions,
 -            field: {
 -                get_context: function () { },
 -                get_domain: function () { return domain;},
 -                get_groupby: function () { }
 -            }
 -        });
 -
 -        // remove all propositions
 -        _.invoke(children, 'destroy');
 -        // add new empty proposition
 -        this.append_proposition();
 -        // TODO: API on searchview
 -        this.view.$el.removeClass('oe_searchview_open_drawer');
 -    }
 -});
 -
  instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{
      template: 'SearchView.extended_search.proposition',
      events: {
          'click .searchview_extended_delete_prop': function (e) {
              e.stopPropagation();
              this.getParent().remove_proposition(this);
 -        }
 +        },
      },
      /**
       * @constructs instance.web.search.ExtendedSearchProposition
          this.value.appendTo($value_loc);
  
      },
 -    get_proposition: function() {
 +    get_filter: function () {
          if (this.attrs.selected === null || this.attrs.selected === undefined)
              return null;
 -        var field = this.attrs.selected;
 -        var op_select = this.$('.searchview_extended_prop_op')[0];
 -        var operator = op_select.options[op_select.selectedIndex];
 +        var field = this.attrs.selected,
 +            op_select = this.$('.searchview_extended_prop_op')[0],
 +            operator = op_select.options[op_select.selectedIndex];
  
          return {
 -            label: this.value.get_label(field, operator),
 -            value: this.value.get_domain(field, operator),
 +            attrs: {
 +                domain: [this.value.get_domain(field, operator)],
 +                string: this.value.get_label(field, operator),
 +            },
 +            children: [],
 +            tag: 'filter',
          };
 -    }
 +    },
  });
  
  instance.web.search.ExtendedSearchProposition.Field = instance.web.Widget.extend({
@@@ -2111,6 -2349,7 +2115,6 @@@ instance.web.search.AutoComplete = inst
          this.delay = options.delay;
          this.select = options.select,
          this.get_search_string = options.get_search_string;
 -        this.width = options.width || 400;
  
          this.current_result = null;
  
      },
      start: function () {
          var self = this;
 -        this.$el.width(this.width);
          this.$input.on('keyup', function (ev) {
              if (ev.which === $.ui.keyCode.RIGHT) {
                  self.searching = true;
                  ev.preventDefault();
                  return;
              }
+             // ENTER is caugth at KeyUp rather than KeyDown to avoid firing
+             // before all regular keystrokes have been processed
+             if (ev.which === $.ui.keyCode.ENTER) {
+                 if (self.current_result && self.get_search_string().length) {
+                     self.select_item(ev);
+                 }
+                 return;
+             }
              if (!self.searching) {
                  self.searching = true;
                  return;
          });
          this.$input.on('keydown', function (ev) {
              switch (ev.which) {
+                 // TAB and direction keys are handled at KeyDown because KeyUp
+                 // is not guaranteed to fire.
+                 // See e.g. https://github.com/aef-/jquery.masterblaster/issues/13
                  case $.ui.keyCode.TAB:
-                 case $.ui.keyCode.ENTER:
                      if (self.current_result && self.get_search_string().length) {
                          self.select_item(ev);
                      }
@@@ -85,6 -85,8 +85,6 @@@ instance.web.FormView = instance.web.Vi
       * @param {instance.web.Session} session the current openerp session
       * @param {instance.web.DataSet} dataset the dataset this view will work with
       * @param {String} view_id the identifier of the OpenERP view object
 -     * @param {Object} options
 -     *                  - resize_textareas : [true|false|max_height]
       *
       * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
       */
          this.fields_order = [];
          this.datarecord = {};
          this._onchange_specs = {};
-         this.onchanges_defs = [];
+         this.onchanges_mutex = new $.Mutex();
          this.default_focus_field = null;
          this.default_focus_button = null;
          this.fields_registry = instance.web.form.widgets;
                  opacity: '1',
                  filter: 'alpha(opacity = 100)'
              });
 +            instance.web.bus.trigger('form_view_shown', self);
          });
      },
      do_hide: function () {
              this.$pager.remove();
          if (this.get("actual_mode") === "create")
              return;
 -        this.$pager = $(QWeb.render("FormView.pager", {'widget':self})).hide();
 +        this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
          if (this.options.$pager) {
              this.$pager.appendTo(this.options.$pager);
          } else {
                  def = self.alive(new instance.web.Model(self.dataset.model).call(
                      "onchange", [ids, values, trigger_field_name, onchange_specs, context]));
              }
-             var onchange_def = def.then(function(response) {
-                 if (widget && widget.field['change_default']) {
-                     var fieldname = widget.name;
-                     var value_;
-                     if (response.value && (fieldname in response.value)) {
-                         // Use value from onchange if onchange executed
-                         value_ = response.value[fieldname];
-                     } else {
-                         // otherwise get form value for field
-                         value_ = self.fields[fieldname].get_value();
-                     }
-                     var condition = fieldname + '=' + value_;
+             this.onchanges_mutex.exec(function(){
+                 return def.then(function(response) {
+                     if (widget && widget.field['change_default']) {
+                         var fieldname = widget.name;
+                         var value_;
+                         if (response.value && (fieldname in response.value)) {
+                             // Use value from onchange if onchange executed
+                             value_ = response.value[fieldname];
+                         } else {
+                             // otherwise get form value for field
+                             value_ = self.fields[fieldname].get_value();
+                         }
+                         var condition = fieldname + '=' + value_;
  
-                     if (value_) {
-                         return self.alive(new instance.web.Model('ir.values').call(
-                             'get_defaults', [self.model, condition]
-                         )).then(function (results) {
-                             if (!results.length) {
+                         if (value_) {
+                             return self.alive(new instance.web.Model('ir.values').call(
+                                 'get_defaults', [self.model, condition]
+                             )).then(function (results) {
+                                 if (!results.length) {
+                                     return response;
+                                 }
+                                 if (!response.value) {
+                                     response.value = {};
+                                 }
+                                 for(var i=0; i<results.length; ++i) {
+                                     // [whatever, key, value]
+                                     var triplet = results[i];
+                                     response.value[triplet[1]] = triplet[2];
+                                 }
                                  return response;
-                             }
-                             if (!response.value) {
-                                 response.value = {};
-                             }
-                             for(var i=0; i<results.length; ++i) {
-                                 // [whatever, key, value]
-                                 var triplet = results[i];
-                                 response.value[triplet[1]] = triplet[2];
-                             }
-                             return response;
-                         });
+                             });
+                         }
                      }
-                 }
-                 return response;
-             }).then(function(response) {
-                 return self.on_processed_onchange(response);
+                     return response;
+                 }).then(function(response) {
+                     return self.on_processed_onchange(response);
+                 });
              });
-             this.onchanges_defs.push(onchange_def);
-             return onchange_def;
+             return this.onchanges_mutex.def;
          } catch(e) {
              console.error(e);
              instance.webclient.crashmanager.show_message(e);
          var self = this;
          return this.mutating_mutex.exec(function() {
              function iterate() {
-                 var start = $.Deferred();
-                 start.resolve();
-                 start = _.reduce(self.onchanges_defs, function(memo, d){
-                     return memo.then(function(){
-                         return d;
-                     }, function(){
-                         return d;
-                     });
-                 }, start);
-                 var defs = [start];
+                 var mutex = new $.Mutex();
                  _.each(self.fields, function(field) {
-                     defs.push(field.commit_value());
+                     self.onchanges_mutex.def.then(function(){
+                         mutex.exec(function(){
+                             return field.commit_value();
+                         });
+                     });
                  });
                  var args = _.toArray(arguments);
-                 return $.when.apply($, defs).then(function() {
+                 return $.when.apply(null, [mutex.def, self.onchanges_mutex.def]).then(function() {
                      var save_obj = self.save_list.pop();
                      if (save_obj) {
                          return self._process_save(save_obj).then(function() {
       * if the current record is not yet saved. It will then stay in create mode.
       */
      to_edit_mode: function() {
-         this.onchanges_defs = [];
+         this.onchanges_mutex = new $.Mutex();
          this._actualize_mode("edit");
      },
      /**
                  if (menu) {
                      menu.do_reload_needaction();
                  }
 +                instance.web.bus.trigger('form_view_saved', self);
              });
          }).always(function(){
              $(e.target).attr("disabled", false);
@@@ -2337,58 -2335,29 +2335,58 @@@ instance.web.form.KanbanSelection = ins
      init: function (field_manager, node) {
          this._super(field_manager, node);
      },
 +    start: function () {
 +        var self = this;
 +        this.states = [];
 +        this._super.apply(this, arguments);
 +        // hook on form view content changed: recompute the states, because it may be related to the current stage
 +        this.getParent().on('view_content_has_changed', self, function () {
 +            self.render_value();
 +        });
 +    },
      prepare_dropdown_selection: function() {
          var self = this;
          var data = [];
 -        var selection = self.field.selection || [];
 -        _.map(selection, function(res) {
 -            var value = {
 -                'name': res[0],
 -                'tooltip': res[1],
 -                'state_name': res[1],
 -            }
 -            if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
 -            else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
 -            else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
 -            data.push(value);
 +        var selection = this.field.selection || [];
 +        var stage_id = _.isArray(this.view.datarecord.stage_id) ? this.view.datarecord.stage_id[0] : this.view.datarecord.stage_id;
 +        var legend_field = this.options && this.options.states_legend_field || false;
 +        var fields_to_read = _.map(
 +            this.options && this.options.states_legend || {},
 +            function (value, key, list) { return value; });
 +        if (legend_field && fields_to_read && stage_id) {
 +            var fetch_stage = new openerp.web.DataSet(
 +                this,
 +                self.view.fields[legend_field].field.relation).read_ids([stage_id],
 +                fields_to_read);
 +        }
 +        else { var fetch_stage = $.Deferred().resolve(false); }
 +        return $.when(fetch_stage).then(function (stage_data) {
 +            _.map(selection, function(res) {
 +                var value = {
 +                    'name': res[0],
 +                    'tooltip': res[1],
 +                    'state_name': res[1],
 +                }
 +                if (stage_data && stage_data[0][self.options.states_legend[res[0]]]) {
 +                    value['state_name'] = stage_data[0][self.options.states_legend[res[0]]];
 +                }
 +                if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
 +                else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
 +                else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
 +                data.push(value);
 +            });
 +            return data;
          });
 -        return data;
      },
      render_value: function() {
          var self = this;
          this.record_id = this.view.datarecord.id;
 -        this.states = this.prepare_dropdown_selection();;
 -        this.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
 -        this.$el.find('li').on('click', this.set_kanban_selection.bind(this));
 +        var dd_fetched = this.prepare_dropdown_selection();;
 +        return $.when(dd_fetched).then(function (states) {
 +            self.states = states;
 +            self.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
 +            self.$el.find('li').on('click', self.set_kanban_selection.bind(self));
 +        })
      },
      /* setting the value: in view mode, perform an asynchronous call and reload
      the form view; in edit mode, use set_value to save the new value that will
@@@ -2643,10 -2612,10 +2641,10 @@@ instance.web.form.FieldCharDomain = ins
  
  instance.web.DateTimeWidget = instance.web.Widget.extend({
      template: "web.datepicker",
 -    jqueryui_object: 'datetimepicker',
      type_of_date: "datetime",
      events: {
 -        'change .oe_datepicker_master': 'change_datetime',
 +        'dp.change .oe_datepicker_main': 'change_datetime',
 +        'dp.show .oe_datepicker_main': 'set_datetime_default',
          'keypress .oe_datepicker_master': 'change_datetime',
      },
      init: function(parent) {
      },
      start: function() {
          var self = this;
 +        var l10n = _t.database.parameters;
 +        var options = {
 +            pickTime: true,
 +            useSeconds: true,
 +            startDate: moment({ y: 1900 }),
 +            endDate: moment().add(200, "y"),
 +            calendarWeeks: true,
 +            icons : {
 +                time: 'fa fa-clock-o',
 +                date: 'fa fa-calendar',
 +                up: 'fa fa-chevron-up',
 +                down: 'fa fa-chevron-down'
 +               },
 +            language : moment.locale(),
 +            format : instance.web.normalize_format(l10n.date_format +' '+ l10n.time_format),
 +        };
          this.$input = this.$el.find('input.oe_datepicker_master');
 -        this.$input_picker = this.$el.find('input.oe_datepicker_container');
 -
 -        $.datepicker.setDefaults({
 -            clearText: _t('Clear'),
 -            clearStatus: _t('Erase the current date'),
 -            closeText: _t('Done'),
 -            closeStatus: _t('Close without change'),
 -            prevText: _t('<Prev'),
 -            prevStatus: _t('Show the previous month'),
 -            nextText: _t('Next>'),
 -            nextStatus: _t('Show the next month'),
 -            currentText: _t('Today'),
 -            currentStatus: _t('Show the current month'),
 -            monthNames: Date.CultureInfo.monthNames,
 -            monthNamesShort: Date.CultureInfo.abbreviatedMonthNames,
 -            monthStatus: _t('Show a different month'),
 -            yearStatus: _t('Show a different year'),
 -            weekHeader: _t('Wk'),
 -            weekStatus: _t('Week of the year'),
 -            dayNames: Date.CultureInfo.dayNames,
 -            dayNamesShort: Date.CultureInfo.abbreviatedDayNames,
 -            dayNamesMin: Date.CultureInfo.shortestDayNames,
 -            dayStatus: _t('Set DD as first week day'),
 -            dateStatus: _t('Select D, M d'),
 -            firstDay: Date.CultureInfo.firstDayOfWeek,
 -            initStatus: _t('Select a date'),
 -            isRTL: false
 -        });
 -        $.timepicker.setDefaults({
 -            timeOnlyTitle: _t('Choose Time'),
 -            timeText: _t('Time'),
 -            hourText: _t('Hour'),
 -            minuteText: _t('Minute'),
 -            secondText: _t('Second'),
 -            currentText: _t('Now'),
 -            closeText: _t('Done')
 -        });
 -
 -        this.picker({
 -            onClose: this.on_picker_select,
 -            onSelect: this.on_picker_select,
 -            changeMonth: true,
 -            changeYear: true,
 -            showWeek: true,
 -            showButtonPanel: true,
 -            firstDay: Date.CultureInfo.firstDayOfWeek
 -        });
 -        // Some clicks in the datepicker dialog are not stopped by the
 -        // datepicker and "bubble through", unexpectedly triggering the bus's
 -        // click event. Prevent that.
 -        this.picker('widget').click(function (e) { e.stopPropagation(); });
 -
 -        this.$el.find('img.oe_datepicker_trigger').click(function() {
 -            if (self.get("effective_readonly") || self.picker('widget').is(':visible')) {
 -                self.$input.focus();
 -                return;
 -            }
 -            self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date());
 -            self.$input_picker.show();
 -            self.picker('show');
 -            self.$input_picker.hide();
 -        });
 +        if (this.type_of_date === 'date') {
 +            options['pickTime'] = false;
 +            options['useSeconds'] = false;
 +            options['format'] = instance.web.normalize_format(l10n.date_format);
 +        }
 +        this.picker = this.$('.oe_datepicker_main').datetimepicker(options);
          this.set_readonly(false);
          this.set({'value': false});
      },
 -    picker: function() {
 -        return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments);
 -    },
 -    on_picker_select: function(text, instance_) {
 -        var date = this.picker('getDate');
 -        this.$input
 -            .val(date ? this.format_client(date) : '')
 -            .change()
 -            .focus();
 -    },
      set_value: function(value_) {
          this.set({'value': value_});
          this.$input.val(value_ ? this.format_client(value_) : '');
      },
      set_value_from_ui_: function() {
          var value_ = this.$input.val() || false;
 -        this.set({'value': this.parse_client(value_)});
 +        this.set_value(this.parse_client(value_));
      },
      set_readonly: function(readonly) {
          this.readonly = readonly;
          this.$input.prop('readonly', this.readonly);
 -        this.$el.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
      },
      is_valid_: function() {
          var value_ = this.$input.val();
      format_client: function(v) {
          return instance.web.format_value(v, {"widget": this.type_of_date});
      },
 +    set_datetime_default: function(){
 +        //when opening datetimepicker the date and time by default should be the one from
 +        //the input field if any or the current day otherwise
 +        if (this.type_of_date === 'datetime') {
 +            value = moment().second(0);
 +            if (this.$input.val().length !== 0 && this.is_valid_()){
 +                var value = this.$input.val();
 +            }
 +            this.$('.oe_datepicker_main').data('DateTimePicker').setValue(value);
 +        }
 +    },
      change_datetime: function(e) {
          if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
              this.set_value_from_ui_();
  });
  
  instance.web.DateWidget = instance.web.DateTimeWidget.extend({
 -    jqueryui_object: 'datepicker',
      type_of_date: "date"
  });
  
@@@ -3186,9 -3196,9 +3184,9 @@@ instance.web.form.FieldRadio = instance
          this._super(field_manager, node);
          this.selection = _.clone(this.field.selection) || [];
          this.domain = false;
 +        this.uniqueId = _.uniqueId("radio");
      },
      initialize_content: function () {
 -        this.uniqueId = _.uniqueId("radio");
          this.on("change:effective_readonly", this, this.render_value);
          this.field_manager.on("view_content_has_changed", this, this.get_selection);
          this.get_selection();
      render_value: function () {
          var self = this;
          this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
 -        this.$("input:checked").prop("checked", false);
          if (this.get_value()) {
              this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
              this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
@@@ -3999,6 -4010,7 +3997,6 @@@ instance.web.form.FieldOne2Many = insta
      disable_utility_classes: true,
      init: function(field_manager, node) {
          this._super(field_manager, node);
 -        lazy_build_o2m_kanban_view();
          this.is_loaded = $.Deferred();
          this.initial_is_loaded = this.is_loaded;
          this.form_last_update = $.Deferred();
          /**
           * Returns the current active view if any.
           */
 -        if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
 -            this.viewmanager.views[this.viewmanager.active_view] &&
 -            this.viewmanager.views[this.viewmanager.active_view].controller) {
 -            return {
 -                type: this.viewmanager.active_view,
 -                controller: this.viewmanager.views[this.viewmanager.active_view].controller
 -            };
 -        }
 +        return (this.viewmanager && this.viewmanager.active_view);
      },
      set_value: function(value_) {
          value_ = value_ || [];
      save_any_view: function() {
          var view = this.get_active_view();
          if (view) {
 -            if (this.viewmanager.active_view === "form") {
 +            if (this.viewmanager.active_view.type === "form") {
                  if (view.controller.is_initialized.state() !== 'resolved') {
                      return $.when(false);
                  }
                  return $.when(view.controller.save());
 -            } else if (this.viewmanager.active_view === "list") {
 +            } else if (this.viewmanager.active_view.type === "list") {
                  return $.when(view.controller.ensure_saved());
              }
          }
          if (!view){
              return true;
          }
 -        switch (this.viewmanager.active_view) {
 +        switch (this.viewmanager.active_view.type) {
          case 'form':
              return _(view.controller.fields).chain()
                  .invoke('is_valid')
@@@ -4293,9 -4312,10 +4291,9 @@@ instance.web.form.One2ManyViewManager 
      template: 'One2Many.viewmanager',
      init: function(parent, dataset, views, flags) {
          this._super(parent, dataset, views, _.extend({}, flags, {$sidebar: false}));
 -        this.registry = this.registry.extend({
 +        this.registry = instance.web.views.extend({
              list: 'instance.web.form.One2ManyListView',
              form: 'instance.web.form.One2ManyFormView',
 -            kanban: 'instance.web.form.One2ManyKanbanView',
          });
          this.__ignore_blur = false;
      },
@@@ -4520,11 -4540,9 +4518,11 @@@ instance.web.form.One2ManyListView = in
              window.confirm = confirm;
          }
      },
 -    reload_record: function (record) {
 -        // Evict record.id from cache to ensure it will be reloaded correctly
 -        this.dataset.evict_record(record.get('id'));
 +    reload_record: function (record, options) {
 +        if (!options || !options['do_not_evict']) {
 +            // Evict record.id from cache to ensure it will be reloaded correctly
 +            this.dataset.evict_record(record.get('id'));
 +        }
  
          return this._super(record);
      }
@@@ -4561,6 -4579,13 +4559,6 @@@ instance.web.form.One2ManyFormView = in
      }
  });
  
 -var lazy_build_o2m_kanban_view = function() {
 -    if (! instance.web_kanban || instance.web.form.One2ManyKanbanView)
 -        return;
 -    instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({
 -    });
 -};
 -
  instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
      template: "FieldMany2ManyTags",
      tag_template: "FieldMany2ManyTag",
@@@ -4866,7 -4891,6 +4864,7 @@@ instance.web.form.Many2ManyListView = i
              this.model,
              {
                  title: _t("Add: ") + this.m2m_field.string,
 +                alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views["form"] : undefined,
                  no_create: this.m2m_field.options.no_create,
              },
              new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
          var pop = new instance.web.form.FormOpenPopup(this);
          pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
              title: _t("Open: ") + this.m2m_field.string,
 +            alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views["form"] : undefined,
              readonly: this.getParent().get("effective_readonly")
          });
          pop.on('write_completed', self, self.reload_content);
@@@ -5155,7 -5178,8 +5153,7 @@@ instance.web.form.AbstractFormPopup = i
          this.domain = domain || [];
          this.context = context || {};
          this.options = options;
 -        _.defaults(this.options, {
 -        });
 +        _.defaults(this.options, {});
      },
      init_dataset: function() {
          var self = this;
          if (this.options.alternative_form_view) {
              this.view_form.set_embedded_view(this.options.alternative_form_view);
          }
 -        this.view_form.appendTo(this.$el.find(".oe_popup_form"));
 +        this.view_form.appendTo(this.$(".oe_popup_form").show());
          this.view_form.on("form_view_loaded", self, function() {
              var multi_select = self.row_id === null && ! self.options.disable_multiple_selection;
              self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", {
@@@ -5300,20 -5324,22 +5298,20 @@@ instance.web.form.SelectCreatePopup = i
          this.display_popup();
      },
      start: function() {
 -        var self = this;
          this.init_dataset();
          if (this.options.initial_view == "search") {
 -            instance.web.pyeval.eval_domains_and_contexts({
 +            var context = instance.web.pyeval.sync_eval_domains_and_contexts({
                  domains: [],
                  contexts: [this.context]
 -            }).done(function (results) {
 -                var search_defaults = {};
 -                _.each(results.context, function (value_, key) {
 -                    var match = /^search_default_(.*)$/.exec(key);
 -                    if (match) {
 -                        search_defaults[match[1]] = value_;
 -                    }
 -                });
 -                self.setup_search_view(search_defaults);
 +            }).context;
 +            var search_defaults = {};
 +            _.each(context, function (value_, key) {
 +                var match = /^search_default_(.*)$/.exec(key);
 +                if (match) {
 +                    search_defaults[match[1]] = value_;
 +                }
              });
 +            this.setup_search_view(search_defaults);
          } else { // "form"
              this.new_object();
          }
          if (this.searchview) {
              this.searchview.destroy();
          }
 -        if (this.searchview_drawer) {
 -            this.searchview_drawer.destroy();
 -        }
 +        var $buttons = this.$('.o-search-options');
          this.searchview = new instance.web.SearchView(this,
 -                this.dataset, false,  search_defaults);
 -        this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview);
 +                this.dataset, false,  search_defaults, {$buttons: $buttons});
          this.searchview.on('search_data', self, function(domains, contexts, groupbys) {
              if (self.initial_ids) {
                  self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
                  self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys);
              }
          });
 -        this.searchview.on("search_view_loaded", self, function() {
 +        this.searchview.appendTo(this.$(".o-popup-search")).done(function() {
 +            self.searchview.toggle_visibility(true);
              self.view_list = new instance.web.form.SelectCreateListView(self,
                      self.dataset, false,
                      _.extend({'deletable': false,
                  e.cancel = true;
              });
              self.view_list.popup = self;
 -            self.view_list.appendTo($(".oe_popup_list", self.$el)).then(function() {
 +            self.view_list.appendTo(self.$(".oe_popup_list").show()).then(function() {
                  self.view_list.do_show();
              }).then(function() {
                  self.searchview.do_search();
                      self.new_object();
                  });
              });
 -        });
 -        this.searchview.appendTo(this.$(".oe_popup_search"));
 +        });        
      },
      do_search: function(domains, contexts, groupbys) {
          var self = this;
      },
      new_object: function() {
          if (this.searchview) {
 -            this.searchview.hide();
 +            this.searchview.do_hide();
          }
          if (this.view_list) {
              this.view_list.do_hide();
@@@ -6233,12 -6262,7 +6231,12 @@@ instance.web.form.StatInfo = instance.w
              value: this.get("value") || 0,
          };
          if (! this.node.attrs.nolabel) {
 -            options.text = this.string
 +            if(this.options.label_field && this.view.datarecord[this.options.label_field]) {
 +                options.text = this.view.datarecord[this.options.label_field];
 +            }
 +            else {
 +                options.text = this.string;
 +            }
          }
          this.$el.html(QWeb.render("StatInfo", options));
      },
@@@ -171,7 -171,7 +171,7 @@@ instance.web.ListView = instance.web.Vi
  
          var context = _.extend({}, record.attributes, {
              uid: this.session.uid,
 -            current_date: new Date().toString('yyyy-MM-dd')
 +            current_date: moment().format('YYYY-MM-DD')
              // TODO: time, datetime, relativedelta
          });
          var i;
  
          // Pager
          if (!this.$pager) {
 -            this.$pager = $(QWeb.render("ListView.pager", {'widget':self}));
 +            this.$pager = $(QWeb.render("ListView.pager", {'widget':self})).hide();
              if (this.options.$buttons) {
                  this.$pager.appendTo(this.options.$pager);
              } else {
  
          var total = dataset.size();
          var limit = this.limit() || total;
 -        if (total === 0)
 -            this.$pager.hide();
 -        else
 -            this.$pager.css("display", "");
 -        this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
 +        this.$pager.find('.oe-pager-button').toggle(total > limit);
 +        this.$pager.find('.oe_pager_value').toggle(total !== 0);
          var spager = '-';
          if (total) {
              var range_start = this.page * limit + 1;
      },
      reload_record: function (record) {
          var self = this;
+         var fields = this.fields_view.fields;
          return this.dataset.read_ids(
              [record.get('id')],
              _.pluck(_(this.columns).filter(function (r) {
                  self.records.remove(record);
                  return;
              }
-             _(_.keys(values)).each(function(key){
-                 record.set(key, values[key], {silent: true});
+             _.each(values, function (value, key) {
+                 if (fields[key] && fields[key].type === 'many2many')
+                     record.set(key + '__display', false, {silent: true});
+                 record.set(key, value, {silent: true});            
              });
              record.trigger('change', record);
          });
          this.$el.prepend(
              $('<div class="oe_view_nocontent">').html(this.options.action.help)
          );
 -        var create_nocontent = this.$buttons;
 +        var $buttons = this.$buttons;
          this.$el.find('.oe_view_nocontent').click(function() {
 -            create_nocontent.openerpBounce();
 +            $buttons.width($buttons.width() + 1).openerpBounce();
          });
      }
  });
@@@ -1514,10 -1520,12 +1517,10 @@@ instance.web.ListView.Groups = instance
                                  }))
                              .end()
                              .find('button[data-pager-action=previous]')
 -                                .css('visibility',
 -                                     page === 0 ? 'hidden' : '')
 +                                .toggleClass('disabled', page === 0)
                              .end()
                              .find('button[data-pager-action=next]')
 -                                .css('visibility',
 -                                     page === pages - 1 ? 'hidden' : '');
 +                                .toggleClass('disabled', page === pages - 1);
                      }
                  }
  
                      self.setup_resequence_rows(list, dataset);
                  }).always(function() {
                      if (post_render) { post_render(); }
 +                    self.view.trigger('view_list_rendered');
                  });
              });
          return $el;
@@@ -2149,8 -2156,7 +2152,8 @@@ instance.web.list.columns = new instanc
      'button': 'instance.web.list.Button',
      'field.many2onebutton': 'instance.web.list.Many2OneButton',
      'field.reference': 'instance.web.list.Reference',
 -    'field.many2many': 'instance.web.list.Many2Many'
 +    'field.many2many': 'instance.web.list.Many2Many',
 +    'button.toggle_button': 'instance.web.list.toggle_button',
  });
  instance.web.list.columns.for_ = function (id, field, node) {
      var description = _.extend({tag: node.tag}, field, node.attrs);
@@@ -2389,18 -2395,5 +2392,18 @@@ instance.web.list.Reference = instance.
          return this._super(row_data, options);
      }
  });
 +instance.web.list.toggle_button = instance.web.list.Column.extend({
 +    format: function (row_data, options) {
 +        this._super(row_data, options);
 +        var button_tips = JSON.parse(this.options);
 +        var fieldname = this.field_name;
 +        var has_value = row_data[fieldname] && !!row_data[fieldname].value;
 +        this.icon = has_value ? 'gtk-yes' : 'gtk-normal';
 +        this.string = has_value ? _t(button_tips ? button_tips['active']: ''): _t(button_tips ? button_tips['inactive']: '');
 +        return QWeb.render('toggle_button', {
 +            widget: this,
 +            prefix: instance.session.prefix,
 +        });
 +    },
 +});
  })();
 -// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
@@@ -7,6 -7,7 +7,6 @@@
              <script type="text/javascript" src="/web/static/lib/es5-shim/es5-shim.min.js"></script>
              <script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
              <script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
 -            <script type="text/javascript" src="/web/static/lib/datejs/globalization/en-US.js"></script>
              <script type="text/javascript" src="/web/static/lib/spinjs/spin.js"></script>
  
              <!-- jQuery stuff -->
@@@ -22,7 -23,6 +22,7 @@@
              <script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
              <script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
              <script type="text/javascript" src="/web/static/src/js/tour.js"></script>
 +            <script type="text/javascript" src="/web/static/test/menu.js"></script>
  
              <link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css"/>
          </template>
          </template>
  
          <template id="web.assets_backend">
 -            <!-- Datejs -->
 -            <script type="text/javascript" src="/web/static/lib/datejs/core.js"></script>
 -            <script type="text/javascript" src="/web/static/lib/datejs/parser.js"></script>
 -            <script type="text/javascript" src="/web/static/lib/datejs/sugarpak.js"></script>
 -            <script type="text/javascript" src="/web/static/lib/datejs/extras.js"></script>
 -
              <!-- jQuery addons -->
              <script type="text/javascript" src="/web/static/lib/jquery.validate/jquery.validate.js"></script>
              <script type="text/javascript" src="/web/static/lib/jquery.autosize/jquery.autosize.js"></script>
              <link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css"/>
              <script type="text/javascript" src="/web/static/lib/bootstrap/js/bootstrap.js"></script>
  
 +            <!-- Datetime Picker -->
 +            <link rel="stylesheet" href="/web/static/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css"/>
 +            <script type="text/javascript" src="/web/static/lib/moment/moment.js"></script>
 +            <script type="text/javascript" src="/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js"></script>
 +
              <!-- jQuery ui -->
              <link rel="stylesheet" href="/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css"/>
  
@@@ -70,6 -71,9 +70,6 @@@
                  <script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
              </t>
  
 -            <link rel="stylesheet" href="/web/static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css"/>
 -            <script type="text/javascript" src="/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js"></script>
 -
              <link rel="stylesheet" href="/web/static/lib/jquery.ui.notify/css/ui.notify.css"/>
              <script type="text/javascript" src="/web/static/lib/jquery.ui.notify/js/jquery.notify.js"></script>
  
              <script type="text/javascript" src="/web/static/src/js/view_list.js"></script>
              <script type="text/javascript" src="/web/static/src/js/view_form.js"></script>
              <script type="text/javascript" src="/web/static/src/js/view_list_editable.js"></script>
 +            <script type="text/javascript" src="/web/static/src/js/view_pivot.js"></script>
              <script type="text/javascript" src="/web/static/src/js/view_tree.js"></script>
              <script type="text/javascript" src="/base/static/src/js/apps.js"></script>
  
                  <span class="oe_logo_edit">Edit Company data</span>
                  <img src='/web/binary/company_logo'/>
              </a>
 -            <div>
 -                <div>
 -                    <div class="oe_secondary_menus_container">
 -                        <t t-foreach="menu_data['children']" t-as="menu">
 -                            <div style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu['id']">
 -                                <t t-foreach="menu['children']" t-as="menu">
 -                                    <div class="oe_secondary_menu_section">
 -                                        <t t-esc="menu['name']"/>
 -                                    </div>
 -                                    <t t-call="web.menu_secondary_submenu"/>
 -                                </t>
 +            <div class="oe_secondary_menus_container">
 +                <t t-foreach="menu_data['children']" t-as="menu">
 +                    <div style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu['id']">
 +                        <t t-foreach="menu['children']" t-as="menu">
 +                            <div class="oe_secondary_menu_section">
 +                                <t t-esc="menu['name']"/>
                              </div>
 +                            <t t-call="web.menu_secondary_submenu"/>
                          </t>
                      </div>
 -                </div>
 +                </t>
              </div>
              <div class="oe_footer">
                  Powered by <a href="http://www.openerp.com" target="_blank"><span>Odoo</span></a>
                          <t t-call="web.menu"/>
                      </div>
                  </nav>
 -                <div class="openerp openerp_webclient_container">
 -                    <table class="oe_webclient">
 -                        <tr>
 -                            <td class="oe_leftbar" valign="top">
 -                                <div groups="base.group_user,base.group_portal">
 -                                    <t t-call="web.menu_secondary"/>
 -                                </div>
 -                            </td>
 -                            <td class="oe_application"/>
 -                        </tr>
 -                    </table>
 +                <div class="openerp openerp_webclient_container oe_webclient">
 +                    <div class="oe_leftbar" groups="base.group_user,base.group_portal">
 +                        <t t-call="web.menu_secondary"/>
 +                    </div><div class="oe_application"/>
                  </div>
              </t>
          </template>
                  <t t-set="body_classname" t-value="'oe_single_form'"/>
                  <div class="oe_single_form_container modal-content">
                      <div class="oe_single_form_logo">
-                         <img src="/web/binary/company_logo"/>
+                         <img t-attf-src="/web/binary/company_logo{{ '?dbname='+db if db else '' }}"/>
                      </div>
                      <hr/>
                      <t t-raw="0"/>
@@@ -20,8 -20,8 +20,8 @@@ openerp.web_calendar = function(instanc
      }
  
      function get_fc_defaultOptions() {
 -        shortTimeformat = Date.CultureInfo.formatPatterns.shortTime;
 -        var dateFormat = Date.normalizeFormat(instance.web.strip_raw_chars(_t.database.parameters.date_format));
 +        shortTimeformat = moment._locale._longDateFormat.LT;
 +        var dateFormat = instance.web.normalize_format(_t.database.parameters.date_format);
          return {
              weekNumberTitle: _t("W"),
              allDayText: _t("All day"),
                  week:     _t("Week"),
                  day:      _t("Day")
              },
 -            monthNames: Date.CultureInfo.monthNames,
 -            monthNamesShort: Date.CultureInfo.abbreviatedMonthNames,
 -            dayNames: Date.CultureInfo.dayNames,
 -            dayNamesShort: Date.CultureInfo.abbreviatedDayNames,
 -            firstDay: Date.CultureInfo.firstDayOfWeek,
 +            monthNames: moment.months(),
 +            monthNamesShort: moment.monthsShort(),
 +            dayNames: moment.weekdays(),
 +            dayNamesShort: moment.weekdaysShort(),
 +            firstDay: moment._locale._week.dow,
              weekNumbers: true,
              axisFormat : shortTimeformat.replace(/:mm/,'(:mm)'),
              timeFormat : {
@@@ -88,8 -88,6 +88,8 @@@
              this.range_start = null;
              this.range_stop = null;
              this.selected_filters = [];
 +
 +            this.shown = $.Deferred();
          },
  
          set_default_options: function(options) {
                  this.info_fields.push(fv.arch.children[fld].attrs.name);
              }
  
 +            self.shown.done(this._do_show_init.bind(this));
              var edit_check = new instance.web.Model(this.dataset.model)
                  .call("check_access_rights", ["write", false])
                  .then(function (write_right) {
                  .call("check_access_rights", ["create", false])
                  .then(function (create_right) {
                      self.create_right = create_right;
 -                    self.init_calendar().then(function() {
 -                        $(window).trigger('resize');
 -                        self.trigger('calendar_view_loaded', fv);
 -                        self.ready.resolve();
 -                    });
 +                    self.ready.resolve();
 +                    self.trigger('calendar_view_loaded', fv);
                  });
              return $.when(edit_check, init);
          },
 -
 +        _do_show_init: function () {
 +            var self = this;
 +            this.init_calendar().then(function() {
 +                $(window).trigger('resize');
 +                self.trigger('calendar_view_loaded', self.fields_view);
 +            });
 +        },
          get_fc_init_options: function () {
              //Documentation here : http://arshaw.com/fullcalendar/docs/
              var self = this;
                          context.$calendar.fullCalendar('changeView','agendaDay');
                      }
                  }
 -                else if (curView.name != "agendaDay" || (curView.name == "agendaDay" && curDate.compareTo(curView.start)===0)) {
 +                else if (curView.name != "agendaDay" || (curView.name == "agendaDay" && moment(curDate).diff(moment(curView.start))===0)) {
                          context.$calendar.fullCalendar('changeView','agendaWeek');
                  }
                  context.$calendar.fullCalendar('gotoDate', obj.currentYear , obj.currentMonth, obj.currentDay);
              }
              else {
                  date_start = instance.web.auto_str_to_date(evt[this.date_start].split(' ')[0],'start');
 -                date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop].split(' ')[0],'start') : null; //.addSeconds(-1) : null;
 +                date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop].split(' ')[0],'start') : null;
              }
  
              if (this.info_fields) {
              }
              
              if (!date_stop && date_delay) {
 -                date_stop = date_start.clone().addHours(date_delay);
 +                var m_start = moment(date_start).add(date_delay,'hours');
 +                date_stop = m_start.toDate();
              }
              var r = {
 -                'start': date_start.toString('yyyy-MM-dd HH:mm:ss'),
 -                'end': date_stop.toString('yyyy-MM-dd HH:mm:ss'),
 +                'start': moment(date_start).format('YYYY-MM-DD HH:mm:ss'),
 +                'end': moment(date_stop).format('YYYY-MM-DD HH:mm:ss'),
                  'title': the_title,
                  'allDay': (this.fields[this.date_start].type == 'date' || (this.all_day && evt[this.all_day]) || false),
                  'id': evt.id,
              var event_end = event.end;
              //Bug when we move an all_day event from week or day view, we don't have a dateend or duration...            
              if (event_end == null) {
 -                event_end = new Date(event.start).addHours(2);
 +                var m_date = moment(event.start).add(2, 'hours');
 +                event_end = m_date.toDate();
              }
  
              if (event.allDay) {
                      event_end = new Date(event.start);
                  }
                  if (this.all_day) {
 -                    //event_end = (new Date(event_end.getTime())).addDays(1);
                      date_start_day = new Date(Date.UTC(event.start.getFullYear(),event.start.getMonth(),event.start.getDate()));
                      date_stop_day = new Date(Date.UTC(event_end.getFullYear(),event_end.getMonth(),event_end.getDate()));                    
                  }
                      date_start_day = new Date(event.start.getFullYear(),event.start.getMonth(),event.start.getDate(),7);
                      date_stop_day = new Date(event_end.getFullYear(),event_end.getMonth(),event_end.getDate(),19);
                  }
 -                data[this.date_start] = instance.web.parse_value(date_start_day, this.fields[this.date_start]);
 +                data[this.date_start] = instance.web.datetime_to_str(date_start_day);
                  if (this.date_stop) {
 -                    data[this.date_stop] = instance.web.parse_value(date_stop_day, this.fields[this.date_stop]);
 +                    data[this.date_stop] = instance.web.datetime_to_str(date_stop_day);
                  }
                  diff_seconds = Math.round((date_stop_day.getTime() - date_start_day.getTime()) / 1000);
                                  
              }
              else {
 -                data[this.date_start] = instance.web.parse_value(event.start, this.fields[this.date_start]);
 +                data[this.date_start] = instance.web.datetime_to_str(event.start);
                  if (this.date_stop) {
 -                    data[this.date_stop] = instance.web.parse_value(event_end, this.fields[this.date_stop]);
 +                    data[this.date_stop] = instance.web.datetime_to_str(event_end);
                  }
                  diff_seconds = Math.round((event_end.getTime() - event.start.getTime()) / 1000);
              }
              return data;
          },
  
 -        do_search: function(domain, context, _group_by) {
 +        do_search: function (domain, context, _group_by) {
 +            var self = this;
 +            this.shown.done(function () {
 +                self._do_search(domain, context, _group_by);
 +            });
 +        },
 +        _do_search: function(domain, context, _group_by) {
              var self = this;
             if (! self.all_filters) {            
                  self.all_filters = {}                
          get_range_domain: function(domain, start, end) {
              var format = instance.web.date_to_str;
              
 -            extend_domain = [[this.date_start, '>=', format(start.clone())],
 -                     [this.date_start, '<=', format(end.clone())]];
 +            extend_domain = [[this.date_start, '>=', format(start)],
 +                     [this.date_start, '<=', format(end)]];
  
              if (this.date_stop) {
                  //add at start 
                  //add at end 
                  extend_domain.push(
                                  '&',
 -                                [this.date_start, '<=', format(start.clone())],
 -                                [this.date_stop, '>=', format(start.clone())],
 +                                [this.date_start, '<=', format(start)],
 +                                [this.date_stop, '>=', format(start)],
                                  '&',
 -                                [this.date_start, '<=', format(end.clone())],
 -                                [this.date_stop, '>=', format(start.clone())]
 +                                [this.date_start, '<=', format(end)],
 +                                [this.date_stop, '>=', format(start)]
                  );
                  //final -> (A & B) | (C & D) | (E & F) ->  | | & A B & C D & E F
              }
              }
              else {
                  var pop = new instance.web.form.FormOpenPopup(this);
-                 pop.show_element(this.dataset.model, parseInt(id), this.dataset.get_context(), {
+                 var id_cast = parseInt(id).toString() == id ? parseInt(id) : id;
+                 pop.show_element(this.dataset.model, id_cast, this.dataset.get_context(), {
                      title: _.str.sprintf(_t("View: %s"),title),
                      view_id: +this.open_popup_action,
-                     res_id: id,
+                     res_id: id_cast,
                      target: 'new',
                      readonly:true
                  });
              return false;
          },
  
 -        do_show: function() {
 +        do_show: function() {            
              if (this.$buttons) {
                  this.$buttons.show();
              }
              this.do_push_state({});
 +            this.shown.resolve();
              return this._super();
          },
          do_hide: function () {
@@@ -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)) {
              self.$('.graph_options_selection label').last().toggle(result);
          });
  
 +        this.$buttons.find('button').tooltip();
 +        
-         return this.model.call('fields_get', []).then(function (f) {
+         return this.model.call('fields_get', {
+                     context: this.graph_view.dataset.context
+                 }).then(function (f) {
              self.fields = f;
              self.fields.__count = {field:'__count', type: 'integer', string:_t('Count')};
              self.groupby_fields = self.get_groupby_fields();
      // 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()));
@@@ -7,6 -7,7 +7,7 @@@ class website_config_settings(osv.osv_m
  
      _columns = {
          'website_id': fields.many2one('website', string="website", required=True),
+         'website_name': fields.related('website_id', 'name', type="char", string="Website Name"),
  
          'language_ids': fields.related('website_id', 'language_ids', type='many2many', relation='res.lang', string='Languages'),
          'default_lang_id': fields.related('website_id', 'default_lang_id', type='many2one', relation='res.lang', string='Default language'),
          'social_linkedin': fields.related('website_id', 'social_linkedin', type="char", string='LinkedIn Account'),
          'social_youtube': fields.related('website_id', 'social_youtube', type="char", string='Youtube Account'),
          'social_googleplus': fields.related('website_id', 'social_googleplus', type="char", string='Google+ Account'),
 +        'compress_html': fields.related('website_id', 'compress_html', type="boolean", string='Compress HTML'),
 +        'cdn_activated': fields.related('website_id', 'cdn_activated', type="boolean", string='Use CDN'),
 +        'cdn_url': fields.related('website_id', 'cdn_url', type="char", string='CDN Base URL'),
 +        'cdn_filters': fields.related('website_id', 'cdn_filters', type="text", string='CDN Filters'),
      }
  
      def on_change_website_id(self, cr, uid, ids, website_id, context=None):
          website_data = self.pool.get('website').read(cr, uid, [website_id], [], context=context)[0]
-         values = {}
+         values = {'website_name': website_data['name']}
          for fname, v in website_data.items():
              if fname in self._columns:
                  values[fname] = v[0] if v and self._columns[fname]._type == 'many2one' else v
@@@ -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)
@@@ -709,12 -684,12 +709,12 @@@ class ir_attachment(osv.osv)
                  result[attach.id] = self.pool['website'].image_url(cr, uid, attach, 'datas')
          return result
      def _datas_checksum(self, cr, uid, ids, name, arg, context=None):
-         return dict(
-             (attach['id'], self._compute_checksum(attach))
-             for attach in self.read(
-                 cr, uid, ids, ['res_model', 'res_id', 'type', 'datas'],
-                 context=context)
-         )
+         result = dict.fromkeys(ids, False)
+         attachments = self.read(cr, uid, ids, ['res_model'], context=context)
+         view_attachment_ids = [attachment['id'] for attachment in attachments if attachment['res_model'] == 'ir.ui.view']
+         for attach in self.read(cr, uid, view_attachment_ids, ['res_model', 'res_id', 'type', 'datas'], context=context):
+             result[attach['id']] = self._compute_checksum(attach)
+         return result
  
      def _compute_checksum(self, attachment_dict):
          if attachment_dict.get('res_model') == 'ir.ui.view'\
              return result
  
          for record in self.browse(cr, uid, ids, context=context):
-             if not record.datas: continue
+             if record.res_model != 'ir.ui.view' or not record.datas: continue
              try:
                  result[record.id] = openerp.tools.image_resize_image_big(record.datas)
              except IOError: # apparently the error PIL.Image.open raises
@@@ -1,4 -1,4 +1,4 @@@
 -@charset "utf-8";
 +@charset "UTF-8";
  /*       THIS CSS FILE IS FOR WEBSITE THEMING CUSTOMIZATION ONLY
   *
   * css for editor buttons, openerp widget included in the website and other
  
  /* Extra Styles */
  img.shadow {
 -  -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
 -  -ms-border-radius: 3px;
 -  -o-border-radius: 3px;
 +  -webkit-border-radius: 3px;
    border-radius: 3px;
 -  -webkit-box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
    -moz-box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
 +  -webkit-box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
    box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.2);
    margin: 0 auto;
  }
@@@ -102,7 -104,7 +102,7 @@@ header a.navbar-brand img 
  }
  
  #wrapwrap p:empty:after {
 -  content: "\2060";
 +  content: "⁠";
  }
  
  /* ----- Snippets Styles ----- */
  #oe_main_menu_navbar {
    min-height: 34px;
    z-index: 1001;
 -  -webkit-border-radius: 0px;
    -moz-border-radius: 0px;
 -  -ms-border-radius: 0px;
 -  -o-border-radius: 0px;
 +  -webkit-border-radius: 0px;
    border-radius: 0px;
    margin-bottom: 0px;
  }
  
  /* ----- BOOTSTRAP HACK FOR STICKY FOOTER ----- */
  html, body, #wrapwrap {
 -  -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
 +  -webkit-box-sizing: border-box;
    box-sizing: border-box;
    height: 100%;
  }
@@@ -190,6 -194,11 +190,11 @@@ footer 
    width: 100%;
  }
  
+ @-moz-document url-prefix() {
+   .table .img-responsive {
+     width: 100%;
+   }
+ }
  /* ---- HACK FOR COVERING UP CK EDITOR BOGUS P INSERTION --- */
  .oe_structure.oe_editable.oe_empty:empty, .oe_editable[data-oe-type=html]:empty, .oe_structure.oe_editable.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child {
    background-image: url("/website/static/src/img/drag_here.png") !important;
    padding-left: 16px;
  }
  
 -#themes-list .well {
 -  padding: 0 0 20px 0;
 -}
 -
  /* -- Hack for removing double scrollbar from mobile preview -- */
  div#mobile-preview.modal {
    overflow: hidden;
@@@ -409,114 -422,6 +414,114 @@@ ul.nav-stacked > li > a 
    margin-right: 32px;
  }
  
 +/* gallery */
 +.o_gallery.o_grid.o_spc-none div.row, .o_gallery.o_masonry.o_spc-none div.row {
 +  margin: 0;
 +}
 +.o_gallery.o_grid.o_spc-none div.row > *, .o_gallery.o_masonry.o_spc-none div.row > * {
 +  padding: 0;
 +}
 +.o_gallery.o_grid.o_spc-small div.row, .o_gallery.o_masonry.o_spc-small div.row {
 +  margin: 5px 0;
 +}
 +.o_gallery.o_grid.o_spc-small div.row > *, .o_gallery.o_masonry.o_spc-small div.row > * {
 +  padding: 0 5px;
 +}
 +.o_gallery.o_grid.o_spc-medium div.row, .o_gallery.o_masonry.o_spc-medium div.row {
 +  margin: 10px 0;
 +}
 +.o_gallery.o_grid.o_spc-medium div.row > *, .o_gallery.o_masonry.o_spc-medium div.row > * {
 +  padding: 0 10px;
 +}
 +.o_gallery.o_grid.o_spc-big div.row, .o_gallery.o_masonry.o_spc-big div.row {
 +  margin: 15px 0;
 +}
 +.o_gallery.o_grid.o_spc-big div.row > *, .o_gallery.o_masonry.o_spc-big div.row > * {
 +  padding: 0 15px;
 +}
 +.o_gallery.o_grid .img, .o_gallery.o_masonry .img {
 +  width: 100%;
 +}
 +.o_gallery.o_grid.size-auto .row {
 +  height: auto;
 +}
 +.o_gallery.o_grid.size-small .row {
 +  height: 100px;
 +}
 +.o_gallery.o_grid.size-medium .row {
 +  height: 250px;
 +}
 +.o_gallery.o_grid.size-big .row {
 +  height: 400px;
 +}
 +.o_gallery.o_grid.size-small .img, .o_gallery.o_grid.size-medium .img, .o_gallery.o_grid.size-big .img {
 +  height: 100%;
 +}
 +.o_gallery.o_nomode.o_spc-none .img {
 +  padding: 0;
 +}
 +.o_gallery.o_nomode.o_spc-small .img {
 +  padding: 5px;
 +}
 +.o_gallery.o_nomode.o_spc-medium .img {
 +  padding: 10px;
 +}
 +.o_gallery.o_nomode.o_spc-big .img {
 +  padding: 15px;
 +}
 +.o_gallery.o_slideshow .carousel ul.carousel-indicators li {
 +  border: 1px solid #aaa;
 +}
 +.o_gallery .carousel-inner .item img {
 +  max-width: none;
 +}
 +
 +.o_gallery.o_slideshow > .container {
 +  height: 100%;
 +}
 +
 +.o_gallery.o_slideshow .carousel, .modal-body.o_slideshow .carousel {
 +  height: 100%;
 +}
 +.o_gallery.o_slideshow .carousel img, .modal-body.o_slideshow .carousel img {
 +  max-height: 100%;
 +  max-width: 100%;
 +  margin: auto;
 +  position: relative;
 +  top: 50%;
 +  -webkit-transform: translateY(-50%);
 +  -ms-transform: translateY(-50%);
 +  transform: translateY(-50%);
 +}
 +.o_gallery.o_slideshow .carousel ul.carousel-indicators, .modal-body.o_slideshow .carousel ul.carousel-indicators {
 +  display: block;
 +  height: auto;
 +  padding: 0;
 +  border-width: 0;
 +  position: absolute;
 +  bottom: 0;
 +}
 +.o_gallery.o_slideshow .carousel ul.carousel-indicators li, .modal-body.o_slideshow .carousel ul.carousel-indicators li {
 +  list-style-image: none;
 +  display: inline-block;
 +  width: 35px;
 +  height: 35px;
 +  margin: 0 0px 5px 5px;
 +  padding: 0;
 +  border: 1px solid #aaa;
 +  text-indent: initial;
 +  background-size: cover;
 +  opacity: 0.5;
 +  background-color: #000;
 +}
 +.o_gallery.o_slideshow .carousel ul.carousel-indicators li.active, .modal-body.o_slideshow .carousel ul.carousel-indicators li.active {
 +  opacity: 1;
 +}
 +.o_gallery.o_slideshow .carousel .carousel-control.left, .o_gallery.o_slideshow .carousel .carousel-control.right, .modal-body.o_slideshow .carousel .carousel-control.left, .modal-body.o_slideshow .carousel .carousel-control.right {
 +  background-image: none;
 +  background-color: transparent;
 +}
 +
  /* Parallax Theme */
  div.carousel .carousel-indicators li {
    border: 1px solid grey;
@@@ -550,7 -455,7 +555,7 @@@ div.carousel div.carousel-content 
    padding: 32px 0;
  }
  
 -/* Background */
 +/* Background (kept for 8.0 compatibility) */
  .oe_dark {
    background: #eff8f8;
    background: rgba(200, 200, 200, 0.14);
  }
  
  .oe_green {
 -  background-color: #169c78;
 +  background-color: #169C78;
    color: white;
  }
  .oe_green .text-muted {
 -  color: #dddddd;
 +  color: #ddd;
  }
  
  .oe_blue_light {
    color: white;
  }
  .oe_blue_light .text-muted {
 -  color: #dddddd;
 +  color: #ddd;
  }
  
  .oe_blue {
    color: white;
  }
  .oe_orange .text-muted {
 -  color: #dddddd;
 +  color: #ddd;
  }
  
  .oe_purple {
    color: white;
  }
  .oe_purple .text-muted {
 -  color: #dddddd;
 +  color: #ddd;
  }
  
  .oe_red {
 -  background-color: #9c1b31;
 +  background-color: #9C1b31;
    color: white;
  }
  .oe_red .text-muted {
 -  color: #dddddd;
 +  color: #ddd;
  }
  
  /* Misc */
@@@ -656,8 -561,10 +661,8 @@@ span[data-oe-type="monetary"] 
  }
  
  .oe_template_fallback {
 -  -webkit-column-count: 3;
    -moz-column-count: 3;
 -  -ms-column-count: 3;
 -  -o-column-count: 3;
 +  -webkit-column-count: 3;
    column-count: 3;
  }
  
@@@ -147,6 -147,10 +147,10 @@@ foote
      float: left
      width: 100%
  
+ @-moz-document url-prefix()
+     .table .img-responsive
+         width: 100%
  /* ---- HACK FOR COVERING UP CK EDITOR BOGUS P INSERTION --- */
  
  .oe_structure.oe_editable.oe_empty:empty, .oe_editable[data-oe-type=html]:empty, .oe_structure.oe_editable.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child
  .nav-hierarchy
      padding-left: 16px
  
 -#themes-list .well
 -    padding: 0 0 20px 0
 -
  /* -- Hack for removing double scrollbar from mobile preview -- */
  div#mobile-preview.modal
      overflow: hidden
@@@ -327,102 -334,6 +331,102 @@@ ul.nav-stacked > li > 
              margin-right: 32px
  
  
 +/* gallery */
 +
 +.o_gallery
 +    &.o_grid, &.o_masonry
 +        &.o_spc-none
 +            div.row
 +                margin: 0
 +            div.row > *
 +                padding: 0
 +        &.o_spc-small
 +            div.row
 +                margin: 5px 0
 +            div.row > *
 +                padding: 0 5px
 +        &.o_spc-medium
 +            div.row
 +                margin: 10px 0
 +            div.row > *
 +                padding: 0 10px
 +        &.o_spc-big
 +            div.row
 +                margin: 15px 0
 +            div.row > *
 +                padding: 0 15px
 +        .img
 +            width: 100%
 +    &.o_grid
 +        &.size-auto .row
 +            height: auto
 +        &.size-small .row
 +            height: 100px
 +        &.size-medium .row
 +            height: 250px
 +        &.size-big .row
 +            height: 400px
 +        &.size-small, &.size-medium, &.size-big
 +            .img
 +                height: 100%
 +    &.o_nomode
 +        &.o_spc-none
 +            .img
 +                padding: 0
 +        &.o_spc-small
 +            .img
 +                padding: 5px
 +        &.o_spc-medium
 +            .img
 +                padding: 10px
 +        &.o_spc-big
 +            .img
 +                padding: 15px
 +
 +    &.o_slideshow .carousel ul.carousel-indicators li
 +        border: 1px solid #aaa
 +    .carousel-inner .item img
 +        max-width: none
 +
 +.o_gallery.o_slideshow > .container
 +    height: 100%
 +
 +.o_gallery.o_slideshow .carousel, .modal-body.o_slideshow .carousel
 +    height: 100%
 +    img
 +        max-height: 100%
 +        max-width: 100%
 +        margin: auto
 +        position: relative
 +        top: 50%
 +        -webkit-transform: translateY(-50%)
 +        -ms-transform: translateY(-50%)
 +        transform: translateY(-50%)
 +    ul.carousel-indicators
 +        display: block
 +        height: auto
 +        padding: 0
 +        border-width: 0
 +        position: absolute
 +        bottom: 0
 +        li
 +            list-style-image: none
 +            display: inline-block
 +            width: 35px
 +            height: 35px
 +            margin: 0 0px 5px 5px
 +            padding: 0
 +            border: 1px solid #aaa
 +            text-indent: initial
 +            background-size: cover
 +            opacity: 0.5
 +            background-color: #000
 +        li.active
 +            opacity: 1
 +    .carousel-control.left, .carousel-control.right
 +        background-image: none
 +        background-color: transparent
 +
  /* Parallax Theme */
  
  div.carousel
              vertical-align: middle
              padding: 32px 0
  
 -/* Background */
 +/* Background (kept for 8.0 compatibility) */
  
  .oe_dark
      background: #eff8f8
              }
          });
  
 +        CKEDITOR.plugins.add('customColor', {
 +            requires: 'panelbutton,floatpanel',
 +            init: function (editor) {
 +                function create_button (buttonID, label) {
 +                    var btnID = buttonID;
 +                    editor.ui.add(buttonID, CKEDITOR.UI_PANELBUTTON, {
 +                        label: label,
 +                        title: label,
 +                        modes: { wysiwyg: true },
 +                        editorFocus: true,
 +                        context: 'font',
 +                        panel: {
 +                            css: [  '/web/css/web.assets_common/' + (new Date().getTime()),
 +                                    '/web/css/website.assets_frontend/' + (new Date().getTime()),
 +                                    '/web/css/website.assets_editor/' + (new Date().getTime())],
 +                            attributes: { 'role': 'listbox', 'aria-label': label },
 +                        },
 +                        enable: function () {
 +                            this.setState(CKEDITOR.TRISTATE_OFF);
 +                        },
 +                        disable: function () {
 +                            this.setState(CKEDITOR.TRISTATE_DISABLED);
 +                        },
 +                        onBlock: function (panel, block) {
 +                            var self = this;
 +                            var html = openerp.qweb.render('website.colorpicker');
 +                            block.autoSize = true;
 +                            block.element.setHtml( html );
 +                            $(block.element.$).on('click', 'button', function () {
 +                                self.clicked(this);
 +                            });
 +                            if (btnID === "TextColor") {
 +                                $(".only-text", block.element.$).css("display", "block");
 +                                $(".only-bg", block.element.$).css("display", "none");
 +                            }
 +                            var $body = $(block.element.$).parents("body");
 +                            setTimeout(function () {
 +                                $body.css('background-color', '#fff');
 +                            }, 0);
 +                        },
 +                        getClasses: function () {
 +                            var self = this;
 +                            var classes = [];
 +                            var id = this._.id;
 +                            var block = this._.panel._.panel._.blocks[id];
 +                            var $root = $(block.element.$);
 +                            $root.find("button").map(function () {
 +                                var color = self.getClass(this);
 +                                if(color) classes.push( color );
 +                            });
 +                            return classes;
 +                        },
 +                        getClass: function (button) {
 +                            var color = btnID === "BGColor" ? $(button).attr("class") : $(button).attr("class").replace(/^bg-/i, 'text-');
 +                            return color.length && color;
 +                        },
 +                        clicked: function (button) {
 +                            var className = this.getClass(button);
 +                            var ancestor = editor.getSelection().getCommonAncestor();
 +
 +                            editor.focus();
 +                            this._.panel.hide();
 +                            editor.fire('saveSnapshot');
 +
 +                            // remove style
 +                            var classes = [];
 +                            var $ancestor = $(ancestor.$);
 +                            var $fonts = $(ancestor.$).find('font');
 +                            if (!ancestor.$.tagName) {
 +                                $ancestor = $ancestor.parent();
 +                            }
 +                            if ($ancestor.is('font')) {
 +                                $fonts = $fonts.add($ancestor[0]);
 +                            }
 +
 +                            $fonts.filter("."+this.getClasses().join(",.")).map(function () {
 +                                var className = $(this).attr("class");
 +                                if (classes.indexOf(className) === -1) {
 +                                    classes.push(className);
 +                                }
 +                            });
 +                            for (var k in classes) {
 +                                editor.removeStyle( new CKEDITOR.style({
 +                                    element: 'font',
 +                                    attributes: { 'class': classes[k] },
 +                                }) );
 +                            }
 +
 +                            // add new style
 +                            if (className) {
 +                                editor.applyStyle( new CKEDITOR.style({
 +                                    element: 'font',
 +                                    attributes: { 'class': className },
 +                                }) );
 +                            }
 +                            editor.fire('saveSnapshot');
 +                        }
 +
 +                    });
 +                }
 +                create_button("BGColor", "Background Color");
 +                create_button("TextColor", "Text Color");
 +            }
 +        });
 +
          CKEDITOR.plugins.add('oeref', {
              requires: 'widget',
  
                  fillEmptyBlocks: false,
                  filebrowserImageUploadUrl: "/website/attach",
                  // Support for sharedSpaces in 4.x
 -                extraPlugins: 'sharedspace,customdialogs,tablebutton,oeref',
 +                extraPlugins: 'customColor,sharedspace,customdialogs,tablebutton,oeref',
                  // Place toolbar in controlled location
                  sharedSpaces: { top: 'oe_rte_toolbar' },
                  toolbar: [{
                      {name: "Heading 5", element: 'h5'},
                      {name: "Heading 6", element: 'h6'},
                      {name: "Formatted", element: 'pre'},
 -                    {name: "Address", element: 'address'}
 +                    {name: "Address", element: 'address'},
                  ],
              };
          },
              }
              var callback = _.uniqueId('func_');
              this.$('input[name=func]').val(callback);
 -            window[callback] = function (url, error) {
 +            window[callback] = function (attachments, error) {
                  delete window[callback];
 -                self.file_selected(url, error);
 +                self.file_selected(attachments[0]['website_url'], error);
              };
          },
          file_selection: function () {
                          .attr('data-size', size)
                          .addClass(size)
                          .addClass(no_sizes);
-                 if ((size && _.contains(classes, size)) || (classes[2] === "" && !selected)) {
+                 if ((size && _.contains(classes, size)) || (size === "" && !selected)) {
                      this.$preview.append($p.clone());
                      this.$('#fa-size').val(size);
                      $p.addClass('font-icons-selected');
@@@ -13,8 -13,9 +13,9 @@@
                          <button string="Cancel" type="object" name="cancel" class="oe_link"/>
                      </header>
                      <div>
 -                        <field name="website_id" invisible="True" on_change="on_change_website_id(website_id)"/>
 +                        <field name="website_id" on_change="on_change_website_id(website_id)"/>
                          <group string="Domain">
+                             <field name="website_name" />
                              <label for="google_analytics_key"/>
                              <div name="google_analytics_key">
                                  <div>
                                  name="%(website.action_website_menu)d"
                                  string="Configure website menus" class="oe_link"/>
                          </group>
 +                        <group string="Advanced">
 +                            <label for="compress_html"/>
 +                            <div name="compress_html">
 +                                <div class="oe_inline">
 +                                    <field name="compress_html"/> Compress rendered html for a better google pagespeed result
 +                                </div>
 +                            </div>
 +                            <field name="cdn_activated"/>
 +                            <field name="cdn_url" attrs="{'invisible': [('cdn_activated', '=', False)] }"/>
 +                            <field name="cdn_filters" attrs="{'invisible': [('cdn_activated', '=', False)] }"/>
 +                        </group>
                      </div>
                  </form>
              </field>
@@@ -12,7 -12,6 +12,7 @@@
              <field name="url" eval="'/blog/'+str(ref('website_blog.blog_blog_1'))"/>
              <field name="parent_id" ref="website.main_menu"/>
              <field name="sequence" type="int">40</field>
 +            <field name="website_id" ref="website.default_website"/>
          </record>
      </data>
  
              <field name="state">open</field>
          </record>
  
-         <!-- Post-related subtypes for messaging / Chatter -->
-         <record id="mt_blog_post_new" model="mail.message.subtype">
-             <field name="name">New Post</field>
-             <field name="res_model">blog.post</field>
-             <field name="default" eval="True"/>
-             <field name="description">New Post</field>
-         </record>
-         <record id="mt_blog_post_published" model="mail.message.subtype">
-             <field name="name">Post Published</field>
-             <field name="res_model">blog.post</field>
-             <field name="default" eval="False"/>
-             <field name="description">Post Published</field>
-         </record>
-         <!-- Project-related subtypes for messaging / Chatter -->
-         <record id="mt_blog_blog_post_new" model="mail.message.subtype">
-             <field name="name">New Post</field>
+         <!-- Blog-related subtypes for messaging / Chatter -->
+         <record id="mt_blog_blog_published" model="mail.message.subtype">
+             <field name="name">Published Post</field>
              <field name="res_model">blog.blog</field>
              <field name="default" eval="True"/>
-             <field name="parent_id" eval="ref('mt_blog_post_new')"/>
-             <field name="relation_field">blog_id</field>
+             <field name="description">Published Post</field>
          </record>
  
      </data>
  </openerp>
@@@ -7,6 -7,7 +7,7 @@@ import rando
  
  from openerp import tools
  from openerp import SUPERUSER_ID
+ from openerp.addons.website.models.website import slug
  from openerp.osv import osv, fields
  from openerp.tools.translate import _
  
@@@ -22,6 -23,32 +23,32 @@@ class Blog(osv.Model)
          'description': fields.text('Description'),
      }
  
+     def all_tags(self, cr, uid, ids, min_limit=1, context=None):
+         req = """
+             SELECT
+                 p.blog_id, count(*), r.blog_tag_id
+             FROM
+                 blog_post_blog_tag_rel r
+                     join blog_post p on r.blog_post_id=p.id
+             WHERE
+                 p.blog_id in %s
+             GROUP BY
+                 p.blog_id,
+                 r.blog_tag_id
+             ORDER BY
+                 count(*) DESC
+         """
+         cr.execute(req, [tuple(ids)])
+         tag_by_blog = {i: [] for i in ids}
+         for blog_id, freq, tag_id in cr.fetchall():
+             if freq >= min_limit:
+                 tag_by_blog[blog_id].append(tag_id)
+         tag_obj = self.pool['blog.tag']
+         for blog_id in tag_by_blog:
+             tag_by_blog[blog_id] = tag_obj.browse(cr, uid, tag_by_blog[blog_id], context=context)
+         return tag_by_blog
  
  class BlogTag(osv.Model):
      _name = 'blog.tag'
@@@ -30,6 -57,9 +57,9 @@@
      _order = 'name'
      _columns = {
          'name': fields.char('Name', required=True),
+         'post_ids': fields.many2many(
+             'blog.post', string='Posts',
+         ),
      }
  
  
@@@ -71,6 -101,10 +101,6 @@@ class BlogPost(osv.Model)
              string='Website Messages',
              help="Website communication history",
          ),
 -        'history_ids': fields.one2many(
 -            'blog.post.history', 'post_id',
 -            'History', help='Last post modifications',
 -        ),
          # creation / update stuff
          'create_date': fields.datetime(
              'Created on',
              self.pool['mail.message'].write(cr, SUPERUSER_ID, msg_ids, {'path': new_attribute}, context=context)
          return content
  
 -    def create_history(self, cr, uid, ids, vals, context=None):
 -        for i in ids:
 -            history = self.pool.get('blog.post.history')
 -            if vals.get('content'):
 -                res = {
 -                    'content': vals.get('content', ''),
 -                    'post_id': i,
 -                }
 -                history.create(cr, uid, res)
 -
+     def _check_for_publication(self, cr, uid, ids, vals, context=None):
+         if vals.get('website_published'):
+             base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
+             for post in self.browse(cr, uid, ids, context=context):
+                 post.blog_id.message_post(
+                     body='<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>' % {
+                         'post_publication': _('A new post %s has been published on the %s blog.') % (post.name, post.blog_id.name),
+                         'post_link': _('Click here to access the post.'),
+                         'base_url': base_url,
+                         'blog_slug': slug(post.blog_id),
+                         'post_slug': slug(post),
+                     },
+                     subtype='website_blog.mt_blog_blog_published',
+                     context=context)
+             return True
+         return False
      def create(self, cr, uid, vals, context=None):
          if context is None:
              context = {}
              vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
          create_context = dict(context, mail_create_nolog=True)
          post_id = super(BlogPost, self).create(cr, uid, vals, context=create_context)
 -        self.create_history(cr, uid, [post_id], vals, context)
+         self._check_for_publication(cr, uid, [post_id], vals, context=context)
          return post_id
  
      def write(self, cr, uid, ids, vals, context=None):
          if 'content' in vals:
              vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
          result = super(BlogPost, self).write(cr, uid, ids, vals, context)
 -        self.create_history(cr, uid, ids, vals, context)
+         self._check_for_publication(cr, uid, ids, vals, context=context)
          return result
 -
 -class BlogPostHistory(osv.Model):
 -    _name = "blog.post.history"
 -    _description = "Blog Post History"
 -    _order = 'id DESC'
 -    _rec_name = "create_date"
 -
 -    _columns = {
 -        'post_id': fields.many2one('blog.post', 'Blog Post'),
 -        'summary': fields.char('Summary', select=True),
 -        'content': fields.text("Content"),
 -        'create_date': fields.datetime("Date"),
 -        'create_uid': fields.many2one('res.users', "Modified By"),
 -    }
 -
 -    def getDiff(self, cr, uid, v1, v2, context=None):
 -        history_pool = self.pool.get('blog.post.history')
 -        text1 = history_pool.read(cr, uid, [v1], ['content'])[0]['content']
 -        text2 = history_pool.read(cr, uid, [v2], ['content'])[0]['content']
 -        line1 = line2 = ''
 -        if text1:
 -            line1 = text1.splitlines(1)
 -        if text2:
 -            line2 = text2.splitlines(1)
 -        if (not line1 and not line2) or (line1 == line2):
 -            raise osv.except_osv(_('Warning!'), _('There are no changes in revisions.'))
 -        diff = difflib.HtmlDiff()
 -        return diff.make_table(line1, line2, "Revision-%s" % (v1), "Revision-%s" % (v2), context=True)
@@@ -1,6 -1,8 +1,7 @@@
  id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink\r
  blog_blog_all,blog.blog,model_blog_blog,,1,0,0,0\r
+ blog_blog,blog.blog,model_blog_blog,base.group_document_user,1,1,1,1\r
  blog_post_all,blog.post,model_blog_post,,1,0,0,0\r
  blog_post,blog.post,model_blog_post,base.group_document_user,1,1,1,1\r
 -blog_post_history,blog.post.history,model_blog_post_history,base.group_document_user,1,0,1,0\r
  blog_tag,blog.tag,model_blog_tag,,1,0,0,0\r
  blog_tag_edition,blog.tag,model_blog_tag,base.group_document_user,1,1,1,1\r
          </record>
          <menuitem id="menu_blog" parent="menu_wiki" name="Blogs" action="action_blog_blog" sequence="20"/>
  
 -        <!-- History Tree view -->
 -        <record model="ir.ui.view" id="view_blog_history_tree">
 -            <field name="name">blog.post.history.tree</field>
 -            <field name="model">blog.post.history</field>
 -            <field name="arch" type="xml">
 -                <tree string="Document History">
 -                    <field name="create_date"/>
 -                    <field name="create_uid"/>
 -                    <field name="post_id"/>
 -                </tree>
 -            </field>
 -        </record>
 -        <!-- History Form view  -->
 -        <record model="ir.ui.view" id="view_blog_history_form">
 -            <field name="name">blog.post.history.form</field>
 -            <field name="model">blog.post.history</field>
 -            <field name="arch" type="xml">
 -                <form string="Blog Post History">
 -                    <label for="post_id" class="oe_edit_only"/>
 -                    <h1><field name="post_id" select="1" /></h1>
 -                    <label for="create_date" class="oe_edit_only"/>
 -                    <field name="create_date" readonly="1"/>
 -                </form>
 -            </field>
 -        </record>
 -        <!-- History Action  -->
 -        <record model="ir.actions.act_window" id="action_history">
 -            <field name="name">Page history</field>
 -            <field name="res_model">blog.post.history</field>
 -            <field name="view_type">form</field>
 -            <field name="view_mode">tree,form</field>
 -        </record>
 -        <menuitem id="menu_page_history" parent="menu_wiki" name="Pages history" action="action_history" sequence="30" groups="base.group_no_one"/>
 -        <act_window
 -            id="action_related_page_history"
 -            context="{'search_default_post_id': [active_id], 'default_post_id': active_id}"
 -            domain="[('post_id','=',active_id)]"
 -            name="Page History"
 -            res_model="blog.post.history"
 -            src_model="blog.post"/>
 -
+         <record model="ir.ui.view" id="blog_tag_tree">
+             <field name="name">blog_tag_tree</field>
+             <field name="model">blog.tag</field>
+             <field name="arch" type="xml">
+                 <tree string="Tag List" create="false">
+                     <field name="name"/>
+                     <field name="post_ids"/>
+                 </tree>
+             </field>
+         </record>
+         <record model="ir.ui.view" id="blog_tag_form">
+             <field name="name">blog_tag_form</field>
+             <field name="model">blog.tag</field>
+             <field name="arch" type="xml">
+                 <form string="Tag Form">
+                     <sheet>
+                         <group>
+                             <field name="name"/>
+                         </group>
+                         <label for="post_ids" string="Used in: "/>
+                         <field name="post_ids"/>
+                     </sheet>
+                 </form>
+             </field>
+         </record>
+         <record model="ir.actions.act_window" id="action_tags">
+             <field name="name">Blog Tags</field>
+             <field name="res_model">blog.tag</field>
+             <field name="view_type">form</field>
+             <field name="view_mode">tree,form,graph</field>
+             <field name="view_id" ref="blog_tag_tree"/>
+         </record>
+         <menuitem id="menu_blog_tag" parent="menu_wiki" name="Blog Tags" action="action_tags" sequence="40" />
      </data>
  </openerp>
@@@ -72,14 -72,13 +72,13 @@@ class contactus(http.Controller)
          # fields validation : Check that required field from model crm_lead exists
          error = set(field for field in _REQUIRED if not values.get(field))
  
-         values = dict(values, error=error)
          if error:
-             values.update(kwargs=kwargs.items())
+             values = dict(values, error=error, kwargs=kwargs.items())
              return request.website.render(kwargs.get("view_from", "website.contactus"), values)
  
          try:
              values['medium_id'] = request.registry['ir.model.data'].get_object_reference(request.cr, SUPERUSER_ID, 'crm', 'crm_tracking_medium_website')[1]
 -            values['section_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'website.salesteam_website_sales')
 +            values['team_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'website.salesteam_website_sales')
          except ValueError:
              pass
  
@@@ -3,10 -3,9 +3,10 @@@
  import werkzeug.urls
  import werkzeug.wrappers
  import simplejson
 +import lxml
 +from urllib2 import urlopen
  
  from openerp import tools
 -from openerp import SUPERUSER_ID
  from openerp.addons.web import http
  from openerp.addons.web.controllers.main import login_redirect
  from openerp.addons.web.http import request
@@@ -21,28 -20,31 +21,28 @@@ class WebsiteForum(http.Controller)
      _user_per_page = 30
  
      def _get_notifications(self):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        Message = request.registry['mail.message']
 -        badge_st_id = request.registry['ir.model.data'].xmlid_to_res_id(cr, uid, 'gamification.mt_badge_granted')
 -        if badge_st_id:
 -            msg_ids = Message.search(cr, uid, [('subtype_id', '=', badge_st_id), ('to_read', '=', True)], context=context)
 -            msg = Message.browse(cr, uid, msg_ids, context=context)
 +        badge_subtype = request.env.ref('gamification.mt_badge_granted')
 +        if badge_subtype:
 +            msg = request.env['mail.message'].search([('subtype_id', '=', badge_subtype.id), ('to_read', '=', True)])
          else:
              msg = list()
          return msg
  
      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,
 +            'user': request.env.user,
 +            'is_public_user': request.env.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['forum'] = request.registry['forum.forum'].browse(request.cr, request.uid, kwargs.pop('forum_id'), context=request.context)
 +            values['forum'] = request.env['forum.forum'].browse(kwargs.pop('forum_id'))
          values.update(kwargs)
          return values
  
@@@ -51,8 -53,7 +51,8 @@@
  
      @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)
 +        if request.env.uid != request.website.user_id.id:
 +            request.env.user.send_forum_validation_email(forum_id=forum_id)
          request.session['validation_email_sent'] = True
          return True
  
@@@ -63,7 -64,7 +63,7 @@@
                  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)
 +        done = request.env['res.users'].sudo().browse(int(id)).process_forum_validation_token(token, email, forum_id=forum_id)[0]
          if done:
              request.session['validation_email_done'] = True
          if forum_id:
  
      @http.route(['/forum'], type='http', auth="public", website=True)
      def forum(self, **kwargs):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        Forum = request.registry['forum.forum']
 -        obj_ids = Forum.search(cr, uid, [], context=context)
 -        forums = Forum.browse(cr, uid, obj_ids, context=context)
 +        forums = request.env['forum.forum'].search([])
          return request.website.render("website_forum.forum_all", {'forums': forums})
  
      @http.route('/forum/new', type='http', auth="user", methods=['POST'], website=True)
      def forum_create(self, forum_name="New Forum", **kwargs):
 -        forum_id = request.registry['forum.forum'].create(request.cr, request.uid, {
 -            'name': forum_name,
 -        }, context=request.context)
 -        return request.redirect("/forum/%s" % forum_id)
 +        forum_id = request.env['forum.forum'].create({'name': forum_name})
 +        return request.redirect("/forum/%s" % slug(forum_id))
  
      @http.route('/forum/notification_read', type='json', auth="user", methods=['POST'], website=True)
      def notification_read(self, **kwargs):
 -        request.registry['mail.message'].set_message_read(request.cr, request.uid, [int(kwargs.get('notification_id'))], read=True, context=request.context)
 +        request.env['mail.message'].browse([int(kwargs.get('notification_id'))]).set_message_read(read=True)
          return True
  
      @http.route(['/forum/<model("forum.forum"):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):
 -        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)
 +    def questions(self, forum, tag=None, page=1, filters='all', sorting=None, search='', post_type=None, **post):
 +        Post = request.env['forum.post']
  
          domain = [('forum_id', '=', forum.id), ('parent_id', '=', False), ('state', '=', 'active')]
          if search:
          if filters == 'unanswered':
              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'
 +            domain += [('message_follower_ids', '=', request.env.user.partner_id.id)]
 +        if post_type:
 +            domain += [('post_type', '=', post_type)]
 +
 +        if not sorting:
 +            sorting = forum.default_order
 +
 +        question_count = Post.search_count(domain)
  
 -        question_count = Post.search(cr, uid, domain, count=True, context=context)
          if tag:
              url = "/forum/%s/tag/%s/questions" % (slug(forum), slug(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)
 -        question_ids = Post.browse(cr, uid, obj_ids, context=context)
 +        question_ids = Post.search(domain, limit=self._post_per_page, offset=pager['offset'], order=sorting)
  
          values = self._prepare_forum_values(forum=forum, searches=post)
          values.update({
              'filters': filters,
              'sorting': sorting,
              'search': search,
 +            'post_type': post_type,
          })
          return request.website.render("website_forum.forum_index", values)
  
          return request.website.render("website_forum.faq", values)
  
      @http.route('/forum/get_tags', type='http', auth="public", methods=['GET'], website=True)
 -    def tag_read(self, q='', l=25, t='texttext', **post):
 -        data = request.registry['forum.tag'].search_read(
 -            request.cr,
 -            request.uid,
 +    def tag_read(self, q='', l=25, **post):
 +        data = request.env['forum.tag'].search_read(
              domain=[('name', '=ilike', (q or '') + "%")],
              fields=['id', 'name'],
              limit=int(l),
 -            context=request.context
          )
 -        if t == 'texttext':
 -            # old tag with texttext - Retro for V8 - #TODO Remove in master
 -            data = [tag['name'] for tag in data]
          return simplejson.dumps(data)
  
      @http.route(['/forum/<model("forum.forum"):forum>/tag'], type='http', auth="public", website=True)
      def tags(self, forum, page=1, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        Tag = request.registry['forum.tag']
 -        obj_ids = Tag.search(cr, uid, [('forum_id', '=', forum.id), ('posts_count', '>', 0)], limit=None, order='posts_count DESC', context=context)
 -        tags = Tag.browse(cr, uid, obj_ids, context=context)
 +        tags = request.env['forum.tag'].search([('forum_id', '=', forum.id), ('posts_count', '>', 0)], limit=None, order='posts_count DESC')
          values = self._prepare_forum_values(forum=forum, searches={'tags': True}, **post)
          values.update({
              'tags': tags,
      # 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']
 -        Forum = request.registry['forum.forum']
 -        question_tag_ids = []
 -        tag_version = post.get('tag_type', 'texttext')
 -        if tag_version == "texttext":  # TODO Remove in master
 -            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}))
 -                question_tag_ids = {forum.id: question_tag_ids}
 -        elif tag_version == "select2":
 -            question_tag_ids = Forum._tag_to_write_vals(cr, uid, [forum.id], post.get('question_tags', ''), context)
 -
 -        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[forum.id],
 -            }, 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):
 -        cr, uid, context = request.cr, request.uid, request.context
          # increment view counter
 -        request.registry['forum.post'].set_viewed(cr, SUPERUSER_ID, [question.id], context=context)
 -
 +        question.sudo().set_viewed()
          if question.parent_id:
              redirect_url = "/forum/%s/question/%s" % (slug(forum), slug(question.parent_id))
              return werkzeug.utils.redirect(redirect_url, 301)
 -
          filters = 'question'
          values = self._prepare_forum_values(forum=forum, searches=post)
          values.update({
              favourite_ids = [(4, request.uid)]
          else:
              favourite_ids = [(3, request.uid)]
 -        request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'favourite_ids': favourite_ids}, context=request.context)
 +        question.sudo().write({'favourite_ids': favourite_ids})
          return favourite
  
      @http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/ask_for_close', type='http', auth="user", methods=['POST'], website=True)
      def question_ask_for_close(self, forum, question, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        Reason = request.registry['forum.post.reason']
 -        reason_ids = Reason.search(cr, uid, [], context=context)
 -        reasons = Reason.browse(cr, uid, reason_ids, context)
 +        reasons = request.env['forum.post.reason'].search([])
  
          values = self._prepare_forum_values(**post)
          values.update({
              '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):
  
      @http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/close', type='http', auth="user", methods=['POST'], website=True)
      def question_close(self, forum, question, **post):
 -        request.registry['forum.post'].close(request.cr, request.uid, [question.id], reason_id=int(post.get('reason_id', False)), context=request.context)
 +        question.close(reason_id=int(post.get('reason_id', False)))
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
      @http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/reopen', type='http', auth="user", methods=['POST'], website=True)
      def question_reopen(self, forum, question, **kwarg):
 -        request.registry['forum.post'].reopen(request.cr, request.uid, [question.id], context=request.context)
 +        question.reopen()
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
      @http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/delete', type='http', auth="user", methods=['POST'], website=True)
      def question_delete(self, forum, question, **kwarg):
 -        request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'active': False}, context=request.context)
 +        question.active = False
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
      @http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/undelete', type='http', auth="user", methods=['POST'], website=True)
      def question_undelete(self, forum, question, **kwarg):
 -        request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'active': True}, context=request.context)
 +        question.active = True
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
      # 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>/ask'], type='http', auth="public", website=True)
 +    def forum_post(self, forum, post_type=None, **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)
 +        user = request.env.user
 +        if not post_type in ['question', 'link', 'discussion']:  # fixme: make dynamic
 +            return werkzeug.utils.redirect('/forum/%s' % slug(forum))
          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, {
 -                '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)))
 +            return werkzeug.utils.redirect("/forum/%s/user/%s/edit?email_required=1" % (slug(forum), request.session.uid))
 +        values = self._prepare_forum_values(forum=forum, searches={}, header={'ask_hide': True})
 +        return request.website.render("website_forum.new_%s" % post_type, values)
 +
 +    @http.route(['/forum/<model("forum.forum"):forum>/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=None, post_type=None, **post):
 +        cr, uid, context = request.cr, request.uid, request.context
 +        if not request.session.uid:
 +            return login_redirect()
 +        post_tag_ids = forum._tag_to_write_vals(post.get('post_tags', ''))
 +        new_question = request.env['forum.post'].create({
 +            'forum_id': forum.id,
 +            '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,
 +            'post_type': post_parent and post_parent.post_type or post_type,  # tde check in selection field
 +        })
 +        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):
          if not request.session.uid:
              return login_redirect()
          question = post.parent_id if post.parent_id else post
 -        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'].message_post(
 -                cr, uid, post.id,
 +            post.with_context(mail_create_nosubcribe=True).message_post(
                  body=kwargs.get('comment'),
                  type='comment',
 -                subtype='mt_comment',
 -                context=dict(context, mail_create_nosubcribe=True))
 +                subtype='mt_comment')
          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)
      def post_toggle_correct(self, forum, post, **kwargs):
 -        cr, uid, context = request.cr, request.uid, request.context
          if post.parent_id is False:
              return request.redirect('/')
          if not request.session.uid:
              return {'error': 'anonymous_user'}
  
          # set all answers to False, only one can be accepted
 -        request.registry['forum.post'].write(cr, uid, [c.id for c in post.parent_id.child_ids if not c.id == post.id], {'is_correct': False}, context=context)
 -        request.registry['forum.post'].write(cr, uid, [post.id], {'is_correct': not post.is_correct}, context=context)
 +        (post.parent_id.child_ids - post).write(dict(is_correct=False))
 +        post.is_correct = not post.is_correct
          return post.is_correct
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/delete', type='http', auth="user", methods=['POST'], website=True)
      def post_delete(self, forum, post, **kwargs):
          question = post.parent_id
 -        request.registry['forum.post'].unlink(request.cr, request.uid, [post.id], context=request.context)
 +        post.unlink()
          if question:
              werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
          return werkzeug.utils.redirect("/forum/%s" % slug(forum))
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/edit', type='http', auth="user", website=True)
      def post_edit(self, forum, post, **kwargs):
 -        tag_version = kwargs.get('tag_type', 'texttext')
 -        if tag_version == "texttext":  # old version - retro v8 - #TODO Remove in master
 -            tags = ""
 -            for tag_name in post.tag_ids:
 -                tags += tag_name.name + ","
 -        elif tag_version == "select2":  # new version
 -            tags = [dict(id=tag.id, name=tag.name) for tag in post.tag_ids]
 -            tags = simplejson.dumps(tags)
 +        tags = [dict(id=tag.id, name=tag.name) for tag in post.tag_ids]
 +        tags = simplejson.dumps(tags)
          values = self._prepare_forum_values(forum=forum)
 -
          values.update({
              'tags': tags,
              'post': post,
          })
          return request.website.render("website_forum.edit_post", values)
  
 -    @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/edition', type='http', auth="user", website=True)
 -    def post_edit_retro(self, forum, post, **kwargs):
 -        # This function is only there for retrocompatibility between old template using texttext and template using select2
 -        # It should be removed into master  #TODO JKE: remove in master all condition with tag_type
 -        kwargs.update(tag_type="select2")
 -        return self.post_edit(forum, post, **kwargs)
 -
      @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 = []
 -        Tag = request.registry['forum.tag']
 -        Forum = request.registry['forum.forum']
 -        tag_version = kwargs.get('tag_type', 'texttext')
 -        
 +        post_tags = forum._tag_to_write_vals(kwargs.get('post_tag', ''))
          vals = {
 -            'name': kwargs.get('question_name'),
 +            'tag_ids': post_tags,
 +            'name': kwargs.get('post_name'),
              'content': kwargs.get('content'),
          }
 -        if tag_version == "texttext":  # old version - retro v8 - #TODO Remove in master
 -            if kwargs.get('question_tag') and kwargs.get('question_tag').strip('[]'):
 -                tags = kwargs.get('question_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
 -                    else:
 -                        new_tag = Tag.create(cr, uid, {'name': tag, 'forum_id': forum.id}, context=context)
 -                        question_tags.append(new_tag)
 -                vals['tag_ids'] = [(6, 0, question_tags)]
 -        elif tag_version == "select2":  # new version
 -            vals['tag_ids'] = Forum._tag_to_write_vals(cr, uid, [forum.id], kwargs.get('question_tag', ''), context)[forum.id]
 -
 -        request.registry['forum.post'].write(cr, uid, [post.id], vals, context=context)
 +        post.write(vals)
          question = post.parent_id if post.parent_id else post
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
          if request.uid == post.create_uid.id:
              return {'error': 'own_post'}
          upvote = True if not post.user_vote > 0 else False
 -        return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=upvote, context=request.context)
 +        return post.vote(upvote=upvote)
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/downvote', type='json', auth="public", website=True)
      def post_downvote(self, forum, post, **kwargs):
          if request.uid == post.create_uid.id:
              return {'error': 'own_post'}
          upvote = True if post.user_vote < 0 else False
 -        return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=upvote, context=request.context)
 +        return post.vote(upvote=upvote)
  
      # User
      # --------------------------------------------------
                   '/forum/<model("forum.forum"):forum>/users/page/<int:page>'],
                  type='http', auth="public", website=True)
      def users(self, forum, page=1, **searches):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        User = request.registry['res.users']
 -
 +        User = request.env['res.users']
          step = 30
 -        tag_count = User.search(cr, SUPERUSER_ID, [('karma', '>', 1), ('website_published', '=', True)], count=True, context=context)
 +        tag_count = len(User.search([('karma', '>', 1), ('website_published', '=', True)]))
          pager = request.website.pager(url="/forum/%s/users" % slug(forum), total=tag_count, page=page, step=step, scope=30)
 -
 -        obj_ids = User.search(cr, SUPERUSER_ID, [('karma', '>', 1), ('website_published', '=', True)], limit=step, offset=pager['offset'], order='karma DESC', context=context)
 +        user_obj = User.sudo().search([('karma', '>', 1), ('website_published', '=', True)], limit=step, offset=pager['offset'], order='karma DESC')
          # put the users in block of 3 to display them as a table
 -        users = [[] for i in range(len(obj_ids)/3+1)]
 -        for index, user in enumerate(User.browse(cr, SUPERUSER_ID, obj_ids, context=context)):
 -            users[index/3].append(user)
 +        users = [[] for i in range(len(user_obj) / 3 + 1)]
 +        for index, user in enumerate(user_obj):
 +            users[index / 3].append(user)
          searches['users'] = 'True'
  
          values = self._prepare_forum_values(forum=forum, searches=searches)
  
      @http.route(['/forum/<model("forum.forum"):forum>/partner/<int:partner_id>'], type='http', auth="public", website=True)
      def open_partner(self, forum, partner_id=0, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
          if partner_id:
 -            partner = request.registry['res.partner'].browse(cr, SUPERUSER_ID, partner_id, context=context)
 -            if partner.exists() and partner.user_ids:
 +            partner = request.env['res.partner'].sudo().search([('id', '=', partner_id)])
 +            if partner and partner.user_ids:
                  return werkzeug.utils.redirect("/forum/%s/user/%d" % (slug(forum), partner.user_ids[0].id))
          return werkzeug.utils.redirect("/forum/%s" % slug(forum))
  
      @http.route(['/forum/user/<int:user_id>/avatar'], type='http', auth="public", website=True)
      def user_avatar(self, user_id=0, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
          response = werkzeug.wrappers.Response()
 -        User = request.registry['res.users']
 -        Website = request.registry['website']
 -        user = User.browse(cr, SUPERUSER_ID, user_id, context=context)
 +        User = request.env['res.users']
 +        Website = request.env['website']
 +        user = User.sudo().search([('id', '=', user_id)])
          if not user.exists() or (user_id != request.session.uid and user.karma < 1):
              return Website._image_placeholder(response)
 -        return Website._image(cr, SUPERUSER_ID, 'res.users', user.id, 'image', response)
 +        return Website._image('res.users', user.id, 'image', response)
  
      @http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True)
      def open_user(self, forum, user_id=0, **post):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        User = request.registry['res.users']
 -        Post = request.registry['forum.post']
 -        Vote = request.registry['forum.post.vote']
 -        Activity = request.registry['mail.message']
 -        Followers = request.registry['mail.followers']
 -        Data = request.registry["ir.model.data"]
 +        User = request.env['res.users']
 +        Post = request.env['forum.post']
 +        Vote = request.env['forum.post.vote']
 +        Activity = request.env['mail.message']
 +        Followers = request.env['mail.followers']
 +        Data = request.env["ir.model.data"]
  
 -        user = User.browse(cr, SUPERUSER_ID, user_id, context=context)
 -        current_user = User.browse(cr, SUPERUSER_ID, uid, context=context)
 +        user = User.sudo().search([('id', '=', user_id)])
++        current_user = request.env.user.sudo()
 +        if not user or user.karma < 1:
+         # Users with high karma can see users with karma <= 0 for
+         # moderation purposes, IFF they have posted something (see below)
 -        if (not user.exists() or
++        if (not user or
+                (user.karma < 1 and current_user.karma < forum.karma_unlink_all)):
              return werkzeug.utils.redirect("/forum/%s" % slug(forum))
          values = self._prepare_forum_values(forum=forum, **post)
-         if user_id != request.session.uid and not user.website_published:
-             return request.website.render("website_forum.private_profile", values)
          # questions and answers by user
-         user_questions, user_answers = [], []
 -        user_question_ids = Post.search(cr, uid, [
 -                ('parent_id', '=', False),
 -                ('forum_id', '=', forum.id), ('create_uid', '=', user.id),
 -            ], order='create_date desc', context=context)
 +        user_question_ids = Post.search([
 +            ('parent_id', '=', False),
 +            ('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
 +            order='create_date desc')
          count_user_questions = len(user_question_ids)
+         if (user_id != request.session.uid and not
+                 (user.website_published or
+                     (count_user_questions and current_user.karma > forum.karma_unlink_all))):
+             return request.website.render("website_forum.private_profile", values)
          # displaying only the 20 most recent questions
 -        user_questions = Post.browse(cr, uid, user_question_ids[:20], context=context)
 +        user_questions = user_question_ids[:20]
  
 -        user_answer_ids = Post.search(cr, uid, [
 -                ('parent_id', '!=', False),
 -                ('forum_id', '=', forum.id), ('create_uid', '=', user.id),
 -            ], order='create_date desc', context=context)
 +        user_answer_ids = Post.search([
 +            ('parent_id', '!=', False),
 +            ('forum_id', '=', forum.id), ('create_uid', '=', user.id)],
 +            order='create_date desc')
          count_user_answers = len(user_answer_ids)
          # displaying only the 20  most recent answers
 -        user_answers = Post.browse(cr, uid, user_answer_ids[:20], context=context)
 +        user_answers = user_answer_ids[:20]
  
          # showing questions which user following
 -        obj_ids = Followers.search(cr, SUPERUSER_ID, [('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)], context=context)
 -        post_ids = [follower.res_id for follower in Followers.browse(cr, SUPERUSER_ID, obj_ids, context=context)]
 -        que_ids = Post.search(cr, uid, [('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)], context=context)
 -        followed = Post.browse(cr, uid, que_ids, context=context)
 +        post_ids = [follower.res_id for follower in Followers.sudo().search([('res_model', '=', 'forum.post'), ('partner_id', '=', user.partner_id.id)])]
 +        followed = Post.search([('id', 'in', post_ids), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
  
 -        #showing Favourite questions of user.
 -        fav_que_ids = Post.search(cr, uid, [('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)], context=context)
 -        favourite = Post.browse(cr, uid, fav_que_ids, context=context)
 +        # showing Favourite questions of user.
 +        favourite = Post.search([('favourite_ids', '=', user.id), ('forum_id', '=', forum.id), ('parent_id', '=', False)])
  
 -        #votes which given on users questions and answers.
 -        data = Vote.read_group(cr, uid, [('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"], context=context)
 +        # votes which given on users questions and answers.
 +        data = Vote.read_group([('forum_id', '=', forum.id), ('recipient_id', '=', user.id)], ["vote"], groupby=["vote"])
          up_votes, down_votes = 0, 0
          for rec in data:
              if rec['vote'] == '1':
              elif rec['vote'] == '-1':
                  down_votes = rec['vote_count']
  
 -        #Votes which given by users on others questions and answers.
 -        post_votes = Vote.search(cr, uid, [('user_id', '=', user.id)], context=context)
 -        vote_ids = Vote.browse(cr, uid, post_votes, context=context)
 +        # Votes which given by users on others questions and answers.
 +        vote_ids = Vote.search([('user_id', '=', user.id)])
  
 -        #activity by user.
 -        model, comment = Data.get_object_reference(cr, uid, 'mail', 'mt_comment')
 -        activity_ids = Activity.search(cr, uid, [('res_id', 'in', user_question_ids+user_answer_ids), ('model', '=', 'forum.post'), ('subtype_id', '!=', comment)], order='date DESC', limit=100, context=context)
 -        activities = Activity.browse(cr, uid, activity_ids, context=context)
 +        # activity by user.
 +        model, comment = Data.get_object_reference('mail', 'mt_comment')
 +        activities = Activity.search([('res_id', 'in', (user_question_ids + user_answer_ids).ids), ('model', '=', 'forum.post'), ('subtype_id', '!=', comment)],
 +                                     order='date DESC', limit=100)
  
          posts = {}
          for act in activities:
              posts[act.res_id] = True
 -        posts_ids = Post.browse(cr, uid, posts.keys(), context=context)
 +        posts_ids = Post.search([('id', 'in', posts.keys())])
          posts = dict(map(lambda x: (x.id, (x.parent_id or x, x.parent_id and x or False)), posts_ids))
  
          # TDE CLEANME MASTER: couldn't it be rewritten using a 'menu' key instead of one key for each menu ?
 -        if user.id == uid:
 +        if user == request.env.user:
              post['my_profile'] = True
          else:
              post['users'] = True
  
          values.update({
 -            'uid': uid,
 +            'uid': request.env.user.id,
              'user': user,
              'main_object': user,
              'searches': post,
  
      @http.route('/forum/<model("forum.forum"):forum>/user/<model("res.users"):user>/edit', type='http', auth="user", website=True)
      def edit_profile(self, forum, user, **kwargs):
 -        country = request.registry['res.country']
 -        country_ids = country.search(request.cr, SUPERUSER_ID, [], context=request.context)
 -        countries = country.browse(request.cr, SUPERUSER_ID, country_ids, context=request.context)
 +        countries = request.env['res.country'].search([])
          values = self._prepare_forum_values(forum=forum, searches=kwargs)
          values.update({
              'email_required': kwargs.get('email_required'),
          }
          if request.uid == user.id:  # the controller allows to edit only its own privacy settings; use partner management for other cases
              values['website_published'] = kwargs.get('website_published') == 'True'
 -        request.registry['res.users'].write(request.cr, request.uid, [user.id], values, context=request.context)
 +        user.write(values)
          return werkzeug.utils.redirect("/forum/%s/user/%d" % (slug(forum), user.id))
  
      # Badges
  
      @http.route('/forum/<model("forum.forum"):forum>/badge', type='http', auth="public", website=True)
      def badges(self, forum, **searches):
 -        cr, uid, context = request.cr, request.uid, request.context
 -        Badge = request.registry['gamification.badge']
 -        badge_ids = Badge.search(cr, SUPERUSER_ID, [('challenge_ids.category', '=', 'forum')], context=context)
 -        badges = Badge.browse(cr, uid, badge_ids, context=context)
 +        Badge = request.env['gamification.badge']
 +        badges = Badge.sudo().search([('challenge_ids.category', '=', 'forum')])
          badges = sorted(badges, key=lambda b: b.stat_count_distinct, reverse=True)
          values = self._prepare_forum_values(forum=forum, searches={'badges': True})
          values.update({
  
      @http.route(['''/forum/<model("forum.forum"):forum>/badge/<model("gamification.badge"):badge>'''], type='http', auth="public", website=True)
      def badge_users(self, forum, badge, **kwargs):
 -        user_ids = [badge_user.user_id.id for badge_user in badge.owner_ids]
 -        users = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, user_ids, context=request.context)
 +        users = [badge_user.user_id for badge_user in badge.sudo().owner_ids]
          values = self._prepare_forum_values(forum=forum, searches={'badges': True})
          values.update({
              'badge': badge,
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/comment/<model("mail.message"):comment>/convert_to_answer', type='http', auth="user", methods=['POST'], website=True)
      def convert_comment_to_answer(self, forum, post, comment, **kwarg):
 -        new_post_id = request.registry['forum.post'].convert_comment_to_answer(request.cr, request.uid, comment.id, context=request.context)
 -        if not new_post_id:
 +        post = request.env['forum.post'].convert_comment_to_answer(comment.id)
 +        if not post:
              return werkzeug.utils.redirect("/forum/%s" % slug(forum))
 -        post = request.registry['forum.post'].browse(request.cr, request.uid, new_post_id, context=request.context)
          question = post.parent_id if post.parent_id else post
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
  
      @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/convert_to_comment', type='http', auth="user", methods=['POST'], website=True)
      def convert_answer_to_comment(self, forum, post, **kwarg):
          question = post.parent_id
 -        new_msg_id = request.registry['forum.post'].convert_answer_to_comment(request.cr, request.uid, post.id, context=request.context)
 +        new_msg_id = post.convert_answer_to_comment()[0]
          if not new_msg_id:
              return werkzeug.utils.redirect("/forum/%s" % slug(forum))
          return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
      def delete_comment(self, forum, post, comment, **kwarg):
          if not request.session.uid:
              return {'error': 'anonymous_user'}
 -        return request.registry['forum.post'].unlink_comment(request.cr, request.uid, post.id, comment.id, context=request.context)
 +        return post.unlink_comment(comment.id)[0]
  # -*- coding: utf-8 -*-
  
  from datetime import datetime
 +import logging
 +import math
  import uuid
  from werkzeug.exceptions import Forbidden
  
 -import logging
 -import openerp
 -
 -from openerp import api, tools
 +from openerp import _
 +from openerp import api, fields, models
 +from openerp import modules
 +from openerp import tools
  from openerp import SUPERUSER_ID
  from openerp.addons.website.models.website import slug
  from openerp.exceptions import Warning
  
  _logger = logging.getLogger(__name__)
  
 +
  class KarmaError(Forbidden):
      """ Karma-related error, used for forum and posts. """
      pass
  
  
 -class Forum(osv.Model):
 -    """TDE TODO: set karma values for actions dynamic for a given forum"""
 +class Forum(models.Model):
      _name = 'forum.forum'
 -    _description = 'Forums'
 +    _description = 'Forum'
      _inherit = ['mail.thread', 'website.seo.metadata']
  
      def init(self, cr):
 -        """ Add forum uuid for user email validation. """
 +        """ Add forum uuid for user email validation.
 +
 +        TDE TODO: move me somewhere else, auto_init ? """
          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),
 -        'faq': fields.html('Guidelines'),
 -        'description': fields.html('Description'),
 -        # karma generation
 -        '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_answer': fields.integer('Answer a question'),
 -        'karma_edit_own': fields.integer('Edit its own posts'),
 -        'karma_edit_all': fields.integer('Edit all posts'),
 -        'karma_close_own': fields.integer('Close its own posts'),
 -        'karma_close_all': fields.integer('Close all posts'),
 -        'karma_unlink_own': fields.integer('Delete its own posts'),
 -        'karma_unlink_all': fields.integer('Delete 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 answer to all questions'),
 -        'karma_editor_link_files': fields.integer('Linking files (Editor)'),
 -        'karma_editor_clickable_link': fields.integer('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 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'),
 -        'karma_flag': fields.integer('Flag a post as offensive'),
 -    }
 -
 -    def _get_default_faq(self, cr, uid, context=None):
 -        fname = openerp.modules.get_module_resource('website_forum', 'data', 'forum_default_faq.html')
 +    @api.model
 +    def _get_default_faq(self):
 +        fname = modules.get_module_resource('website_forum', 'data', 'forum_default_faq.html')
          with open(fname, 'r') as f:
              return f.read()
          return False
  
 -    _defaults = {
 -        '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
 -        'karma_gen_question_upvote': 5,
 -        'karma_gen_question_downvote': -2,
 -        'karma_gen_answer_upvote': 10,
 -        'karma_gen_answer_downvote': -2,
 -        'karma_gen_answer_accept': 2,
 -        'karma_gen_answer_accepted': 15,
 -        'karma_gen_answer_flagged': -100,
 -        '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_close_all': 500,
 -        'karma_unlink_own': 500,
 -        'karma_unlink_all': 1000,
 -        'karma_upvote': 5,
 -        'karma_downvote': 50,
 -        'karma_answer_accept_own': 20,
 -        'karma_answer_accept_all': 500,
 -        'karma_editor_link_files': 20,
 -        'karma_editor_clickable_link': 20,
 -        'karma_comment_own': 3,
 -        'karma_comment_all': 5,
 -        'karma_comment_convert_own': 50,
 -        'karma_comment_convert_all': 500,
 -        'karma_comment_unlink_own': 50,
 -        'karma_comment_unlink_all': 500,
 -        'karma_retag': 75,
 -        'karma_flag': 500,
 -    }
 -
 -    def create(self, cr, uid, values, context=None):
 -        if context is None:
 -            context = {}
 -        create_context = dict(context, mail_create_nolog=True)
 -        return super(Forum, self).create(cr, uid, values, context=create_context)
 -
 -    def _tag_to_write_vals(self, cr, uid, ids, tags='', context=None):
 -        User = self.pool['res.users']
 -        Tag = self.pool['forum.tag']
 -        result = {}
 -        for forum in self.browse(cr, uid, ids, context=context):
 -            post_tags = []
 -            existing_keep = []
 -            for tag in filter(None, tags.split(',')):
 -                if tag.startswith('_'):  # it's a new tag
 -                    # check that not already created meanwhile or maybe excluded by the limit on the search
 -                    tag_ids = Tag.search(cr, uid, [('name', '=', tag[1:])], context=context)
 -                    if tag_ids:
 -                        existing_keep.append(int(tag_ids[0]))
 -                    else:
 -                        # check if user have Karma needed to create need tag
 -                        user = User.browse(cr, uid, uid, context=context)
 -                        if user.exists() and user.karma >= forum.karma_retag:
 -                            post_tags.append((0, 0, {'name': tag[1:], 'forum_id': forum.id}))
 +    # description and use
 +    name = fields.Char('Forum Name', required=True, translate=True)
 +    faq = fields.Html('Guidelines', default=_get_default_faq, translate=True)
 +    description = fields.Html(
 +        'Description',
 +        default='<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>')
 +    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')],
 +        string='Default Order', required=True, default='write_date desc')
 +    relevancy_post_vote = fields.Float('First Relevancy Parameter', default=0.8)
 +    relevancy_time_decay = fields.Float('Second Relevancy Parameter', default=1.8)
 +    default_post_type = fields.Selection([
 +        ('question', 'Question'),
 +        ('discussion', 'Discussion'),
 +        ('link', 'Link')],
 +        string='Default Post', required=True, default='question')
 +    allow_question = fields.Boolean('Questions', help="Users can answer only once per question. Contributors can edit answers and mark the right ones.", default=True)
 +    allow_discussion = fields.Boolean('Discussions', default=True)
 +    allow_link = fields.Boolean('Links', help="When clicking on the post, it redirects to an external link", default=True)
 +    # karma generation
 +    karma_gen_question_new = fields.Integer(string='Asking a question', default=2)
 +    karma_gen_question_upvote = fields.Integer(string='Question upvoted', default=5)
 +    karma_gen_question_downvote = fields.Integer(string='Question downvoted', default=-2)
 +    karma_gen_answer_upvote = fields.Integer(string='Answer upvoted', default=10)
 +    karma_gen_answer_downvote = fields.Integer(string='Answer downvoted', default=-2)
 +    karma_gen_answer_accept = fields.Integer(string='Accepting an answer', default=2)
 +    karma_gen_answer_accepted = fields.Integer(string='Answer accepted', default=15)
 +    karma_gen_answer_flagged = fields.Integer(string='Answer flagged', default=-100)
 +    # karma-based actions
 +    karma_ask = fields.Integer(string='Ask a new question', default=3)
 +    karma_answer = fields.Integer(string='Answer a question', default=3)
 +    karma_edit_own = fields.Integer(string='Edit its own posts', default=1)
 +    karma_edit_all = fields.Integer(string='Edit all posts', default=300)
 +    karma_close_own = fields.Integer(string='Close its own posts', default=100)
 +    karma_close_all = fields.Integer(string='Close all posts', default=500)
 +    karma_unlink_own = fields.Integer(string='Delete its own posts', default=500)
 +    karma_unlink_all = fields.Integer(string='Delete all posts', default=1000)
 +    karma_upvote = fields.Integer(string='Upvote', default=5)
 +    karma_downvote = fields.Integer(string='Downvote', default=50)
 +    karma_answer_accept_own = fields.Integer(string='Accept an answer on its own questions', default=20)
 +    karma_answer_accept_all = fields.Integer(string='Accept an answers to all questions', default=500)
 +    karma_editor_link_files = fields.Integer(string='Linking files (Editor)', default=20)
 +    karma_editor_clickable_link = fields.Integer(string='Add clickable links (Editor)', default=20)
 +    karma_comment_own = fields.Integer(string='Comment its own posts', default=1)
 +    karma_comment_all = fields.Integer(string='Comment all posts', default=1)
 +    karma_comment_convert_own = fields.Integer(string='Convert its own answers to comments and vice versa', default=50)
 +    karma_comment_convert_all = fields.Integer(string='Convert all answers to answers and vice versa', default=500)
 +    karma_comment_unlink_own = fields.Integer(string='Unlink its own comments', default=50)
 +    karma_comment_unlink_all = fields.Integer(string='Unlinnk all comments', default=500)
 +    karma_retag = fields.Integer(string='Change question tags', default=75)
 +    karma_flag = fields.Integer(string='Flag a post as offensive', default=500)
 +    karma_dofollow = fields.Integer(string='Disabled links', help='If the author has not enough karma, a nofollow attribute is added to links', default=500)
 +
 +    @api.model
 +    def create(self, values):
 +        return super(Forum, self.with_context(mail_create_nolog=True)).create(values)
 +
 +    @api.model
 +    def _tag_to_write_vals(self, tags=''):
 +        User = self.env['res.users']
 +        Tag = self.env['forum.tag']
 +        post_tags = []
++        existing_keep = []
 +        for tag in filter(None, tags.split(',')):
 +            if tag.startswith('_'):  # it's a new tag
 +                # check that not arleady created meanwhile or maybe excluded by the limit on the search
 +                tag_ids = Tag.search([('name', '=', tag[1:])])
 +                if tag_ids:
-                     post_tags.append((4, int(tag_ids[0])))
++                    existing_keep.append(int(tag_ids[0]))
                  else:
 -                    existing_keep.append(int(tag))
 -            post_tags.insert(0, [6, 0, existing_keep])
 -            result[forum.id] = post_tags
 -
 -        return result
 +                    # check if user have Karma needed to create need tag
 +                    user = User.sudo().browse(self._uid)
 +                    if user.exists() and user.karma >= self.karma_retag:
-                             post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
++                        post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
 +            else:
-                 post_tags.append((4, int(tag)))
++                existing_keep.append(int(tag))
++        post_tags.insert(0, [6, 0, existing_keep])
 +        return post_tags
  
  
 -class Post(osv.Model):
 +class Post(models.Model):
      _name = 'forum.post'
      _description = 'Forum Post'
      _inherit = ['mail.thread', 'website.seo.metadata']
      _order = "is_correct DESC, vote_count DESC, write_date DESC"
  
 -    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)
 -        for vote in self.pool['forum.post.vote'].browse(cr, uid, vote_ids, context=context):
 -            res[vote.post_id.id] = vote.vote
 -        return res
 -
 -    def _get_vote_count(self, cr, uid, ids, field_name, arg, context):
 -        res = dict.fromkeys(ids, 0)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            for vote in post.vote_ids:
 -                res[post.id] += int(vote.vote)
 -        return res
 -
 -    def _get_post_from_vote(self, cr, uid, ids, context=None):
 -        result = {}
 -        for vote in self.pool['forum.post.vote'].browse(cr, uid, ids, context=context):
 -            result[vote.post_id.id] = True
 -        return result.keys()
 -
 -    def _get_user_favourite(self, cr, uid, ids, field_name, arg, context):
 -        res = dict.fromkeys(ids, False)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            if uid in [f.id for f in post.favourite_ids]:
 -                res[post.id] = True
 -        return res
 -
 -    def _get_favorite_count(self, cr, uid, ids, field_name, arg, context):
 -        res = dict.fromkeys(ids, 0)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            res[post.id] += len(post.favourite_ids)
 -        return res
 -
 -    def _get_post_from_hierarchy(self, cr, uid, ids, context=None):
 -        post_ids = set(ids)
 -        for post in self.browse(cr, SUPERUSER_ID, ids, context=context):
 -            if post.parent_id:
 -                post_ids.add(post.parent_id.id)
 -        return list(post_ids)
 -
 -    def _get_child_count(self, cr, uid, ids, field_name=False, arg={}, context=None):
 -        res = dict.fromkeys(ids, 0)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            if post.parent_id:
 -                res[post.parent_id.id] = len(post.parent_id.child_ids)
 -            else:
 -                res[post.id] = len(post.child_ids)
 -        return res
 -
 -    def _get_uid_answered(self, cr, uid, ids, field_name, arg, context=None):
 -        res = dict.fromkeys(ids, False)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            res[post.id] = any(answer.create_uid.id == uid for answer in post.child_ids)
 -        return res
 -
 -    def _get_has_validated_answer(self, cr, uid, ids, field_name, arg, context=None):
 -        res = dict.fromkeys(ids, False)
 -        ans_ids = self.search(cr, uid, [('parent_id', 'in', ids), ('is_correct', '=', True)], context=context)
 -        for answer in self.browse(cr, uid, ans_ids, context=context):
 -            res[answer.parent_id.id] = True
 -        return res
 -
 -    def _is_self_reply(self, cr, uid, ids, field_name, arg, context=None):
 -        res = dict.fromkeys(ids, False)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            res[post.id] = post.parent_id and post.parent_id.create_uid == post.create_uid or False
 -        return res
 -
 -    def _get_post_karma_rights(self, cr, uid, ids, field_name, arg, context=None):
 -        user = self.pool['res.users'].browse(cr, uid, uid, context=context)
 -        res = dict.fromkeys(ids, False)
 -        for post in self.browse(cr, uid, ids, context=context):
 -            res[post.id] = {
 -                'karma_ask': post.forum_id.karma_ask,
 -                'karma_answer': post.forum_id.karma_answer,
 -                'karma_accept': post.parent_id and post.parent_id.create_uid.id == uid and post.forum_id.karma_answer_accept_own or post.forum_id.karma_answer_accept_all,
 -                'karma_edit': post.create_uid.id == uid and post.forum_id.karma_edit_own or post.forum_id.karma_edit_all,
 -                'karma_close': post.create_uid.id == uid and post.forum_id.karma_close_own or post.forum_id.karma_close_all,
 -                'karma_unlink': post.create_uid.id == uid and post.forum_id.karma_unlink_own or post.forum_id.karma_unlink_all,
 -                'karma_upvote': post.forum_id.karma_upvote,
 -                'karma_downvote': post.forum_id.karma_downvote,
 -                'karma_comment': post.create_uid.id == uid and post.forum_id.karma_comment_own or post.forum_id.karma_comment_all,
 -                'karma_comment_convert': post.create_uid.id == uid and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all,
 -            }
 -            res[post.id].update({
 -                'can_ask': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_ask'],
 -                'can_answer': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_answer'],
 -                'can_accept': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_accept'],
 -                'can_edit': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_edit'],
 -                'can_close': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_close'],
 -                'can_unlink': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_unlink'],
 -                'can_upvote': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_upvote'],
 -                'can_downvote': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_downvote'],
 -                'can_comment': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_comment'],
 -                'can_comment_convert': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_comment_convert'],
 -            })
 -        return res
 -
 -    _columns = {
 -        'name': fields.char('Title'),
 -        'forum_id': fields.many2one('forum.forum', 'Forum', required=True),
 -        'content': fields.html('Content'),
 -        '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'),
 -        'is_correct': fields.boolean('Valid Answer', help='Correct Answer or Answer on this question accepted.'),
 -        'website_message_ids': fields.one2many(
 -            'mail.message', 'res_id',
 -            domain=lambda self: [
 -                '&', ('model', '=', self._name), ('type', 'in', ['email', 'comment'])
 -            ],
 -            string='Post Messages', help="Comments on forum post",
 -        ),
 -        # history
 -        'create_date': fields.datetime('Asked on', select=True, readonly=True),
 -        'create_uid': fields.many2one('res.users', 'Created by', select=True, readonly=True),
 -        'write_date': fields.datetime('Update on', select=True, readonly=True),
 -        'write_uid': fields.many2one('res.users', 'Updated by', select=True, readonly=True),
 -        # vote fields
 -        'vote_ids': fields.one2many('forum.post.vote', 'post_id', 'Votes'),
 -        'user_vote': fields.function(_get_user_vote, string='My Vote', type='integer'),
 -        'vote_count': fields.function(
 -            _get_vote_count, string="Votes", type='integer',
 -            store={
 -                'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['vote_ids'], 10),
 -                'forum.post.vote': (_get_post_from_vote, [], 10),
 -            }),
 -        # favorite fields
 -        'favourite_ids': fields.many2many('res.users', string='Favourite'),
 -        'user_favourite': fields.function(_get_user_favourite, string="My Favourite", type='boolean'),
 -        'favourite_count': fields.function(
 -            _get_favorite_count, string='Favorite Count', type='integer',
 -            store={
 -                'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['favourite_ids'], 10),
 -            }),
 -        # hierarchy
 -        'parent_id': fields.many2one('forum.post', 'Question', ondelete='cascade'),
 -        'self_reply': fields.function(
 -            _is_self_reply, 'Reply to own question', type='boolean',
 -            store={
 -                'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['parent_id', 'create_uid'], 10),
 -            }),
 -        'child_ids': fields.one2many('forum.post', 'parent_id', 'Answers'),
 -        'child_count': fields.function(
 -            _get_child_count, string="Answers", type='integer',
 -            store={
 -                'forum.post': (_get_post_from_hierarchy, ['parent_id', 'child_ids'], 10),
 -            }),
 -        'uid_has_answered': fields.function(
 -            _get_uid_answered, string='Has Answered', type='boolean',
 -        ),
 -        'has_validated_answer': fields.function(
 -            _get_has_validated_answer, string='Has a Validated Answered', type='boolean',
 -            store={
 -                'forum.post': (_get_post_from_hierarchy, ['parent_id', 'child_ids', 'is_correct'], 10),
 -            }
 -        ),
 -        # closing
 -        'closed_reason_id': fields.many2one('forum.post.reason', 'Reason'),
 -        'closed_uid': fields.many2one('res.users', 'Closed by', select=1),
 -        'closed_date': fields.datetime('Closed on', readonly=True),
 -        # karma
 -        'karma_ask': fields.function(_get_post_karma_rights, string='Karma to ask', type='integer', multi='_get_post_karma_rights'),
 -        'karma_answer': fields.function(_get_post_karma_rights, string='Karma to answer', type='integer', multi='_get_post_karma_rights'),
 -        'karma_accept': fields.function(_get_post_karma_rights, string='Karma to accept this answer', type='integer', multi='_get_post_karma_rights'),
 -        'karma_edit': fields.function(_get_post_karma_rights, string='Karma to edit', type='integer', multi='_get_post_karma_rights'),
 -        'karma_close': fields.function(_get_post_karma_rights, string='Karma to close', type='integer', multi='_get_post_karma_rights'),
 -        'karma_unlink': fields.function(_get_post_karma_rights, string='Karma to unlink', type='integer', multi='_get_post_karma_rights'),
 -        'karma_upvote': fields.function(_get_post_karma_rights, string='Karma to upvote', type='integer', multi='_get_post_karma_rights'),
 -        'karma_downvote': fields.function(_get_post_karma_rights, string='Karma to downvote', type='integer', multi='_get_post_karma_rights'),
 -        'karma_comment': fields.function(_get_post_karma_rights, string='Karma to comment', type='integer', multi='_get_post_karma_rights'),
 -        'karma_comment_convert': fields.function(_get_post_karma_rights, string='karma to convert as a comment', type='integer', multi='_get_post_karma_rights'),
 -        # access rights
 -        'can_ask': fields.function(_get_post_karma_rights, string='Can Ask', type='boolean', multi='_get_post_karma_rights'),
 -        'can_answer': fields.function(_get_post_karma_rights, string='Can Answer', type='boolean', multi='_get_post_karma_rights'),
 -        'can_accept': fields.function(_get_post_karma_rights, string='Can Accept', type='boolean', multi='_get_post_karma_rights'),
 -        'can_edit': fields.function(_get_post_karma_rights, string='Can Edit', type='boolean', multi='_get_post_karma_rights'),
 -        'can_close': fields.function(_get_post_karma_rights, string='Can Close', type='boolean', multi='_get_post_karma_rights'),
 -        'can_unlink': fields.function(_get_post_karma_rights, string='Can Unlink', type='boolean', multi='_get_post_karma_rights'),
 -        'can_upvote': fields.function(_get_post_karma_rights, string='Can Upvote', type='boolean', multi='_get_post_karma_rights'),
 -        'can_downvote': fields.function(_get_post_karma_rights, string='Can Downvote', type='boolean', multi='_get_post_karma_rights'),
 -        'can_comment': fields.function(_get_post_karma_rights, string='Can Comment', type='boolean', multi='_get_post_karma_rights'),
 -        'can_comment_convert': fields.function(_get_post_karma_rights, string='Can Convert to Comment', type='boolean', multi='_get_post_karma_rights'),
 -    }
 -
 -    _defaults = {
 -        'state': 'active',
 -        'views': 0,
 -        'active': True,
 -        'vote_ids': list(),
 -        'favourite_ids': list(),
 -        'child_ids': list(),
 -    }
 -
 -    def create(self, cr, uid, vals, context=None):
 -        if context is None:
 -            context = {}
 -        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)
 +    name = fields.Char('Title')
 +    forum_id = fields.Many2one('forum.forum', string='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', string='Tags')
 +    state = fields.Selection([('active', 'Active'), ('close', 'Close'), ('offensive', 'Offensive')], string='Status', default='active')
 +    views = fields.Integer('Number of Views', default=0)
 +    active = fields.Boolean('Active', default=True)
 +    post_type = fields.Selection([
 +        ('question', 'Question'),
 +        ('link', 'Article'),
 +        ('discussion', 'Discussion')],
 +        string='Type', default='question')
 +    website_message_ids = fields.One2many(
 +        'mail.message', 'res_id',
 +        domain=lambda self: ['&', ('model', '=', self._name), ('type', 'in', ['email', 'comment'])],
 +        string='Post Messages', help="Comments on forum post",
 +    )
 +    # history
 +    create_date = fields.Datetime('Asked on', select=True, readonly=True)
 +    create_uid = fields.Many2one('res.users', string='Created by', select=True, readonly=True)
 +    write_date = fields.Datetime('Update on', select=True, readonly=True)
 +    write_uid = fields.Many2one('res.users', string='Updated by', select=True, readonly=True)
 +    relevancy = fields.Float('Relevancy', compute="_compute_relevancy", store=True)
 +
 +    @api.one
 +    @api.depends('vote_count', 'forum_id.relevancy_post_vote', 'forum_id.relevancy_time_decay')
 +    def _compute_relevancy(self):
 +        days = (datetime.today() - datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days
 +        self.relevancy = math.copysign(1, self.vote_count) * (abs(self.vote_count - 1) ** self.forum_id.relevancy_post_vote / (days + 2) ** self.forum_id.relevancy_time_decay)
 +
 +    # vote
 +    vote_ids = fields.One2many('forum.post.vote', 'post_id', string='Votes')
 +    user_vote = fields.Integer('My Vote', compute='_get_user_vote')
 +    vote_count = fields.Integer('Votes', compute='_get_vote_count', store=True)
 +
 +    @api.multi
 +    def _get_user_vote(self):
 +        votes = self.env['forum.post.vote'].search_read([('post_id', 'in', self._ids), ('user_id', '=', self._uid)], ['vote', 'post_id'])
 +        mapped_vote = dict([(v['post_id'][0], v['vote']) for v in votes])
 +        for vote in self:
 +            vote.user_vote = mapped_vote.get(vote.id, 0)
 +
 +    @api.multi
 +    @api.depends('vote_ids')
 +    def _get_vote_count(self):
 +        read_group_res = self.env['forum.post.vote'].read_group([('post_id', 'in', self._ids)], ['post_id', 'vote'], ['post_id', 'vote'], lazy=False)
 +        result = dict.fromkeys(self._ids, 0)
 +        for data in read_group_res:
 +            result[data['post_id'][0]] += data['__count'] * int(data['vote'])
 +        for post in self:
 +            post.vote_count = result[post.id]
 +
 +    # favorite
 +    favourite_ids = fields.Many2many('res.users', string='Favourite')
 +    user_favourite = fields.Boolean('Is Favourite', compute='_get_user_favourite')
 +    favourite_count = fields.Integer('Favorite Count', compute='_get_favorite_count', store=True)
 +
 +    @api.one
 +    def _get_user_favourite(self):
 +        self.user_favourite = self._uid in self.favourite_ids.ids
 +
 +    @api.one
 +    @api.depends('favourite_ids')
 +    def _get_favorite_count(self):
 +        self.favourite_count = len(self.favourite_ids)
 +
 +    # hierarchy
 +    is_correct = fields.Boolean('Correct', help='Correct answer or answer accepted')
 +    parent_id = fields.Many2one('forum.post', string='Question', ondelete='cascade')
 +    self_reply = fields.Boolean('Reply to own question', compute='_is_self_reply', store=True)
 +    child_ids = fields.One2many('forum.post', 'parent_id', string='Answers')
 +    child_count = fields.Integer('Number of answers', compute='_get_child_count', store=True)
 +    uid_has_answered = fields.Boolean('Has Answered', compute='_get_uid_has_answered')
 +    has_validated_answer = fields.Boolean('Is answered', compute='_get_has_validated_answer', store=True)
 +
 +    @api.multi
 +    @api.depends('create_uid', 'parent_id')
 +    def _is_self_reply(self):
 +        self_replies = self.search([('parent_id.create_uid', '=', self._uid)])
 +        for post in self:
 +            post.is_self_reply = post in self_replies
 +
 +    @api.one
 +    @api.depends('child_ids', 'website_message_ids')
 +    def _get_child_count(self):
 +        def process(node):
 +            total = len(node.website_message_ids) + len(node.child_ids)
 +            for child in node.child_ids:
 +                total += process(child)
 +            return total
 +        self.child_count = process(self)
 +
 +    @api.one
 +    def _get_uid_has_answered(self):
 +        self.uid_has_answered = any(answer.create_uid.id == self._uid for answer in self.child_ids)
 +
 +    @api.multi
 +    @api.depends('child_ids', 'is_correct')
 +    def _get_has_validated_answer(self):
 +        correct_posts = [ans.parent_id for ans in self.search([('parent_id', 'in', self._ids), ('is_correct', '=', True)])]
 +        for post in self:
 +            post.is_correct = post in correct_posts
 +
 +    # closing
 +    closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
 +    closed_uid = fields.Many2one('res.users', string='Closed by', select=1)
 +    closed_date = fields.Datetime('Closed on', readonly=True)
 +    # karma calculation and access
 +    karma_accept = fields.Integer('Convert comment to answer', compute='_get_post_karma_rights')
 +    karma_edit = fields.Integer('Karma to edit', compute='_get_post_karma_rights')
 +    karma_close = fields.Integer('Karma to close', compute='_get_post_karma_rights')
 +    karma_unlink = fields.Integer('Karma to unlink', compute='_get_post_karma_rights')
 +    karma_comment = fields.Integer('Karma to comment', compute='_get_post_karma_rights')
 +    karma_comment_convert = fields.Integer('Karma to convert comment to answer', compute='_get_post_karma_rights')
 +    can_ask = fields.Boolean('Can Ask', compute='_get_post_karma_rights')
 +    can_answer = fields.Boolean('Can Answer', compute='_get_post_karma_rights')
 +    can_accept = fields.Boolean('Can Accept', compute='_get_post_karma_rights')
 +    can_edit = fields.Boolean('Can Edit', compute='_get_post_karma_rights')
 +    can_close = fields.Boolean('Can Close', compute='_get_post_karma_rights')
 +    can_unlink = fields.Boolean('Can Unlink', compute='_get_post_karma_rights')
 +    can_upvote = fields.Boolean('Can Upvote', compute='_get_post_karma_rights')
 +    can_downvote = fields.Boolean('Can Downvote', compute='_get_post_karma_rights')
 +    can_comment = fields.Boolean('Can Comment', compute='_get_post_karma_rights')
 +    can_comment_convert = fields.Boolean('Can Convert to Comment', compute='_get_post_karma_rights')
 +
 +    @api.one
 +    def _get_post_karma_rights(self):
 +        user = self.env.user
 +
 +        self.karma_accept = self.parent_id and self.parent_id.create_uid.id == self._uid and self.forum_id.karma_answer_accept_own or self.forum_id.karma_answer_accept_all
 +        self.karma_edit = self.create_uid.id == self._uid and self.forum_id.karma_edit_own or self.forum_id.karma_edit_all
 +        self.karma_close = self.create_uid.id == self._uid and self.forum_id.karma_close_own or self.forum_id.karma_close_all
 +        self.karma_unlink = self.create_uid.id == self._uid and self.forum_id.karma_unlink_own or self.forum_id.karma_unlink_all
 +        self.karma_comment = self.create_uid.id == self._uid and self.forum_id.karma_comment_own or self.forum_id.karma_comment_all
 +        self.karma_comment_convert = self.create_uid.id == self._uid and self.forum_id.karma_comment_convert_own or self.forum_id.karma_comment_convert_all
 +
 +        self.can_ask = user.karma >= self.forum_id.karma_ask
 +        self.can_answer = user.karma >= self.forum_id.karma_answer
 +        self.can_accept = user.karma >= self.karma_accept
 +        self.can_edit = user.karma >= self.karma_edit
 +        self.can_close = user.karma >= self.karma_close
 +        self.can_unlink = user.karma >= self.karma_unlink
 +        self.can_upvote = user.karma >= self.forum_id.karma_upvote
 +        self.can_downvote = user.karma >= self.forum_id.karma_downvote
 +        self.can_comment = user.karma >= self.karma_comment
 +        self.can_comment_convert = user.karma >= self.karma_comment_convert
 +
 +    @api.model
 +    def create(self, vals):
 +        post = super(Post, self.with_context(mail_create_nolog=True)).create(vals)
 +        # deleted or closed questions
 +        if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active is False):
 +            raise Warning(_('Posting answer on a [Deleted] or [Closed] question is not possible'))
          # karma-based access
          if not post.parent_id and not post.can_ask:
              raise KarmaError('Not enough karma to create a new question')
          elif post.parent_id and not post.can_answer:
              raise KarmaError('Not enough karma to answer to a question')
          # messaging and chatter
 -        base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
 +        base_url = self.env['ir.config_parameter'].get_param('web.base.url')
          if post.parent_id:
              body = _(
                  '<p>A new answer for <i>%s</i> has been posted. <a href="%s/forum/%s/question/%s">Click here to access the post.</a></p>' %
                  (post.parent_id.name, base_url, slug(post.parent_id.forum_id), slug(post.parent_id))
              )
 -            self.message_post(cr, uid, post.parent_id.id, subject=_('Re: %s') % post.parent_id.name, body=body, subtype='website_forum.mt_answer_new', context=context)
 +            post.parent_id.message_post(subject=_('Re: %s') % post.parent_id.name, body=body, subtype='website_forum.mt_answer_new')
          else:
              body = _(
                  '<p>A new question <i>%s</i> has been asked on %s. <a href="%s/forum/%s/question/%s">Click here to access the question.</a></p>' %
                  (post.name, post.forum_id.name, base_url, slug(post.forum_id), slug(post))
              )
 -            self.message_post(cr, uid, post_id, subject=post.name, body=body, subtype='website_forum.mt_question_new', context=context)
 -            self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_question_new, context=context)
 -        return post_id
 +            post.message_post(subject=post.name, body=body, subtype='website_forum.mt_question_new')
 +            self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
 +        return post
  
 -    def write(self, cr, uid, ids, vals, context=None):
 -        posts = self.browse(cr, uid, ids, context=context)
 +    @api.multi
 +    def write(self, vals):
          if 'state' in vals:
 -            if vals['state'] in ['active', 'close'] and any(not post.can_close for post in posts):
 +            if vals['state'] in ['active', 'close'] and any(not post.can_close for post in self):
                  raise KarmaError('Not enough karma to close or reopen a post.')
          if 'active' in vals:
 -            if any(not post.can_unlink for post in posts):
 +            if any(not post.can_unlink for post in self):
                  raise KarmaError('Not enough karma to delete or reactivate a post')
          if 'is_correct' in vals:
 -            if any(not post.can_accept for post in posts):
 +            if any(not post.can_accept for post in self):
                  raise KarmaError('Not enough karma to accept or refuse an answer')
              # update karma except for self-acceptance
              mult = 1 if vals['is_correct'] else -1
 -            for post in self.browse(cr, uid, ids, context=context):
 -                if vals['is_correct'] != post.is_correct and post.create_uid.id != uid:
 -                    self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_answer_accepted * mult, context=context)
 -                    self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_answer_accept * mult, context=context)
 -        if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id'] for key in vals.keys()) and any(not post.can_edit for post in posts):
 +            for post in self:
 +                if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid:
 +                    post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * mult)
 +                    self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult)
 +        if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id'] for key in vals.keys()) and any(not post.can_edit for post in self):
              raise KarmaError('Not enough karma to edit a post.')
  
 -        res = super(Post, self).write(cr, uid, ids, vals, context=context)
 +        res = super(Post, self).write(vals)
          # if post content modify, notify followers
          if 'content' in vals or 'name' in vals:
 -            for post in posts:
 +            for post in self:
                  if post.parent_id:
                      body, subtype = _('Answer Edited'), 'website_forum.mt_answer_edit'
 -                    obj_id = post.parent_id.id
 +                    obj_id = post.parent_id
                  else:
                      body, subtype = _('Question Edited'), 'website_forum.mt_question_edit'
 -                    obj_id = post.id
 -                self.message_post(cr, uid, obj_id, body=body, subtype=subtype, context=context)
 +                    obj_id = post
 +                obj_id.message_post(body=body, subtype=subtype)
          return res
  
 -
 -    def reopen(self, cr, uid, ids, context=None):
 -        if any(post.parent_id or post.state != 'close'
 -                    for post in self.browse(cr, uid, ids, context=context)):
 +    @api.multi
 +    def reopen(self):
 +        if any(post.parent_id or post.state != 'close' for post in self):
              return False
  
 -        reason_offensive = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_7')
 -        reason_spam = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_8')
 -        for post in self.browse(cr, uid, ids, context=context):
 -            if post.closed_reason_id.id in (reason_offensive, reason_spam):
 +        reason_offensive = self.env.ref('website_forum.reason_7')
 +        reason_spam = self.env.ref('website_forum.reason_8')
 +        for post in self:
 +            if post.closed_reason_id in (reason_offensive, reason_spam):
                  _logger.info('Upvoting user <%s>, reopening spam/offensive question',
                               post.create_uid)
                  # TODO: in master, consider making this a tunable karma parameter
 -                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id],
 -                                                 post.forum_id.karma_gen_question_downvote * -5,
 -                                                 context=context)
 -        self.pool['forum.post'].write(cr, SUPERUSER_ID, ids, {'state': 'active'}, context=context)
 +                post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_downvote * -5)
 +
 +        self.sudo().write({'state': 'active'})
  
 -    def close(self, cr, uid, ids, reason_id, context=None):
 -        if any(post.parent_id for post in self.browse(cr, uid, ids, context=context)):
 +    @api.multi
 +    def close(self, reason_id):
 +        if any(post.parent_id for post in self):
              return False
  
 -        reason_offensive = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_7')
 -        reason_spam = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_8')
 +        reason_offensive = self.env.ref('website_forum.reason_7').id
 +        reason_spam = self.env.ref('website_forum.reason_8').id
          if reason_id in (reason_offensive, reason_spam):
 -            for post in self.browse(cr, uid, ids, context=context):
 +            for post in self:
                  _logger.info('Downvoting user <%s> for posting spam/offensive contents',
                               post.create_uid)
                  # TODO: in master, consider making this a tunable karma parameter
 -                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id],
 -                                                 post.forum_id.karma_gen_question_downvote * 5,
 -                                                 context=context)
 +                post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_downvote * 5)
  
 -        self.pool['forum.post'].write(cr, uid, ids, {
 +        self.write({
              'state': 'close',
 -            'closed_uid': uid,
 +            'closed_uid': self._uid,
              'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
              'closed_reason_id': reason_id,
 -        }, context=context)
 +        })
 +        return True
  
 -    def unlink(self, cr, uid, ids, context=None):
 -        posts = self.browse(cr, uid, ids, context=context)
 -        if any(not post.can_unlink for post in posts):
 +    @api.multi
 +    def unlink(self):
 +        if any(not post.can_unlink for post in self):
              raise KarmaError('Not enough karma to unlink a post')
          # if unlinking an answer with accepted answer: remove provided karma
 -        for post in posts:
 +        for post in self:
              if post.is_correct:
 -                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_answer_accepted * -1, context=context)
 -                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_answer_accept * -1, context=context)
 -        return super(Post, self).unlink(cr, uid, ids, context=context)
 -
 -    def vote(self, cr, uid, ids, upvote=True, context=None):
 -        Vote = self.pool['forum.post.vote']
 -        vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
 +                post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
 +                self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
 +        return super(Post, self).unlink()
 +
 +    @api.multi
 +    def vote(self, upvote=True):
 +        Vote = self.env['forum.post.vote']
 +        vote_ids = Vote.search([('post_id', 'in', self._ids), ('user_id', '=', self._uid)])
          new_vote = '1' if upvote else '-1'
          voted_forum_ids = set()
          if vote_ids:
 -            for vote in Vote.browse(cr, uid, vote_ids, context=context):
 +            for vote in vote_ids:
                  if upvote:
                      new_vote = '0' if vote.vote == '-1' else '1'
                  else:
                      new_vote = '0' if vote.vote == '1' else '-1'
 -                Vote.write(cr, uid, vote_ids, {'vote': new_vote}, context=context)
 +                vote.vote = new_vote
                  voted_forum_ids.add(vote.post_id.id)
 -        for post_id in set(ids) - voted_forum_ids:
 -            for post_id in ids:
 -                Vote.create(cr, uid, {'post_id': post_id, 'vote': new_vote}, context=context)
 -        return {'vote_count': self._get_vote_count(cr, uid, ids, None, None, context=context)[ids[0]], 'user_vote': new_vote}
 +        for post_id in set(self._ids) - voted_forum_ids:
 +            for post_id in self._ids:
 +                Vote.create({'post_id': post_id, 'vote': new_vote})
 +        return {'vote_count': self.vote_count, 'user_vote': new_vote}
  
 -    def convert_answer_to_comment(self, cr, uid, id, context=None):
 +    @api.one
 +    def convert_answer_to_comment(self):
          """ Tools to convert an answer (forum.post) to a comment (mail.message).
          The original post is unlinked and a new comment is posted on the question
          using the post create_uid as the comment's author. """
 -        post = self.browse(cr, SUPERUSER_ID, id, context=context)
 -        if not post.parent_id:
 +        if not self.parent_id:
              return False
  
          # karma-based action check: use the post field that computed own/all value
 -        if not post.can_comment_convert:
 +        if not self.can_comment_convert:
              raise KarmaError('Not enough karma to convert an answer to a comment')
  
          # post the message
 -        question = post.parent_id
 +        question = self.parent_id
          values = {
 -            'author_id': post.create_uid.partner_id.id,
 -            'body': html2plaintext(post.content),
 +            'author_id': self.create_uid.partner_id.id,
 +            'body': tools.html2plaintext(self.content),
              'type': 'comment',
              'subtype': 'mail.mt_comment',
 -            'date': post.create_date,
 +            'date': self.create_date,
          }
 -        message_id = self.pool['forum.post'].message_post(
 -            cr, uid, question.id,
 -            context=dict(context, mail_create_nosubcribe=True),
 -            **values)
 +        new_message = self.browse(question.id).with_context(mail_create_nosubcribe=True).message_post(**values)
  
          # unlink the original answer, using SUPERUSER_ID to avoid karma issues
 -        self.pool['forum.post'].unlink(cr, SUPERUSER_ID, [post.id], context=context)
 +        self.sudo().unlink()
  
 -        return message_id
 +        return new_message
  
 -    def convert_comment_to_answer(self, cr, uid, message_id, default=None, context=None):
 +    @api.model
 +    def convert_comment_to_answer(self, message_id, default=None):
          """ Tool to convert a comment (mail.message) into an answer (forum.post).
          The original comment is unlinked and a new answer from the comment's author
          is created. Nothing is done if the comment's author already answered the
          question. """
 -        comment = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
 -        post = self.pool['forum.post'].browse(cr, uid, comment.res_id, context=context)
 -        user = self.pool['res.users'].browse(cr, uid, uid, context=context)
 +        comment = self.env['mail.message'].sudo().browse(message_id)
 +        post = self.browse(comment.res_id)
          if not comment.author_id or not comment.author_id.user_ids:  # only comment posted by users can be converted
              return False
  
          # karma-based action check: must check the message's author to know if own / all
 -        karma_convert = comment.author_id.id == user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all
 -        can_convert = uid == SUPERUSER_ID or user.karma >= karma_convert
 +        karma_convert = comment.author_id.id == self.env.user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all
 +        can_convert = self.env.user.karma >= karma_convert
          if not can_convert:
              raise KarmaError('Not enough karma to convert a comment to an answer')
  
              'parent_id': question.id,
          }
          # done with the author user to have create_uid correctly set
 -        new_post_id = self.pool['forum.post'].create(cr, post_create_uid.id, post_values, context=context)
 +        new_post = self.sudo(post_create_uid.id).create(post_values)
  
          # delete comment
 -        self.pool['mail.message'].unlink(cr, SUPERUSER_ID, [comment.id], context=context)
 +        comment.unlink()
  
 -        return new_post_id
 +        return new_post
  
 -    def unlink_comment(self, cr, uid, id, message_id, context=None):
 -        comment = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
 -        post = self.pool['forum.post'].browse(cr, uid, id, context=context)
 -        user = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
 -        if not comment.model == 'forum.post' or not comment.res_id == id:
 +    @api.one
 +    def unlink_comment(self, message_id):
 +        user = self.env.user
 +        comment = self.env['mail.message'].sudo().browse(message_id)
 +        if not comment.model == 'forum.post' or not comment.res_id == self.id:
              return False
 -
          # karma-based action check: must check the message's author to know if own or all
 -        karma_unlink = comment.author_id.id == user.partner_id.id and post.forum_id.karma_comment_unlink_own or post.forum_id.karma_comment_unlink_all
 -        can_unlink = uid == SUPERUSER_ID or user.karma >= karma_unlink
 +        karma_unlink = comment.author_id.id == user.partner_id.id and self.forum_id.karma_comment_unlink_own or self.forum_id.karma_comment_unlink_all
 +        can_unlink = user.karma >= karma_unlink
          if not can_unlink:
              raise KarmaError('Not enough karma to unlink a comment')
 +        return comment.unlink()
  
 -        return self.pool['mail.message'].unlink(cr, SUPERUSER_ID, [message_id], context=context)
 -
 -    def set_viewed(self, cr, uid, ids, context=None):
 -        cr.execute("""UPDATE forum_post SET views = views+1 WHERE id IN %s""", (tuple(ids),))
 +    @api.multi
 +    def set_viewed(self):
 +        self._cr.execute("""UPDATE forum_post SET views = views+1 WHERE id IN %s""", (self._ids,))
          return True
  
 -    def _get_access_link(self, cr, uid, mail, partner, context=None):
 -        post = self.pool['forum.post'].browse(cr, uid, mail.res_id, context=context)
 +    @api.model
 +    def _get_access_link(self, mail, partner):
 +        post = self.browse(mail.res_id)
          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)
  
              else:
                  post_id = thread_id
              post = self.browse(cr, uid, post_id, context=context)
 +            # TDE FIXME: trigger browse because otherwise the function field is not compted - check with RCO
 +            tmp1, tmp2 = post.karma_comment, post.can_comment
 +            user = self.pool['res.users'].browse(cr, uid, uid)
 +            tmp3 = user.karma
 +            # TDE END FIXME
              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):
 +class PostReason(models.Model):
      _name = "forum.post.reason"
      _description = "Post Closing Reason"
      _order = 'name'
 -    _columns = {
 -        'name': fields.char('Post Reason', required=True, translate=True),
 -    }
  
 +    name = fields.Char(string='Closing Reason', required=True, translate=True)
  
 -class Vote(osv.Model):
 +
 +class Vote(models.Model):
      _name = 'forum.post.vote'
      _description = 'Vote'
 -    _columns = {
 -        'post_id': fields.many2one('forum.post', 'Post', ondelete='cascade', required=True),
 -        'user_id': fields.many2one('res.users', 'User', required=True),
 -        'vote': fields.selection([('1', '1'), ('-1', '-1'), ('0', '0')], 'Vote', required=True),
 -        'create_date': fields.datetime('Create Date', select=True, readonly=True),
 -
 -        # TODO master: store these two
 -        'forum_id': fields.related('post_id', 'forum_id', type='many2one', relation='forum.forum', string='Forum'),
 -        'recipient_id': fields.related('post_id', 'create_uid', type='many2one', relation='res.users', string='To', help="The user receiving the vote"),
 -    }
 -    _defaults = {
 -        'user_id': lambda self, cr, uid, ctx: uid,
 -        'vote': lambda *args: '1',
 -    }
 +
 +    post_id = fields.Many2one('forum.post', string='Post', ondelete='cascade', required=True)
 +    user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self._uid)
 +    vote = fields.Selection([('1', '1'), ('-1', '-1'), ('0', '0')], string='Vote', required=True, default='1')
 +    create_date = fields.Datetime('Create Date', select=True, readonly=True)
 +    forum_id = fields.Many2one('forum.forum', string='Forum', related="post_id.forum_id", store=True)
 +    recipient_id = fields.Many2one('res.users', string='To', related="post_id.create_uid", store=True)
  
      def _get_karma_value(self, old_vote, new_vote, up_karma, down_karma):
          _karma_upd = {
          }
          return _karma_upd[old_vote][new_vote]
  
 -    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)
 +    @api.model
 +    def create(self, vals):
 +        vote = super(Vote, self).create(vals)
  
          # own post check
          if vote.user_id.id == vote.post_id.create_uid.id:
          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:
              karma_value = self._get_karma_value('0', vote.vote, vote.forum_id.karma_gen_question_upvote, vote.forum_id.karma_gen_question_downvote)
 -        self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [vote.recipient_id.id], karma_value, context=context)
 -        return vote_id
 +        vote.recipient_id.sudo().add_karma(karma_value)
 +        return vote
  
 -    def write(self, cr, uid, ids, values, context=None):
 +    @api.multi
 +    def write(self, values):
          if 'vote' in values:
 -            for vote in self.browse(cr, uid, ids, context=context):
 +            for vote in self:
                  # 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_value = self._get_karma_value(vote.vote, values['vote'], vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
                  else:
                      karma_value = self._get_karma_value(vote.vote, values['vote'], vote.forum_id.karma_gen_question_upvote, vote.forum_id.karma_gen_question_downvote)
 -                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [vote.recipient_id.id], karma_value, context=context)
 -        res = super(Vote, self).write(cr, uid, ids, values, context=context)
 +                vote.recipient_id.sudo().add_karma(karma_value)
 +        res = super(Vote, self).write(values)
          return res
  
  
 -class Tags(osv.Model):
 +class Tags(models.Model):
      _name = "forum.tag"
 -    _description = "Tag"
 +    _description = "Forum Tag"
      _inherit = ['website.seo.metadata']
  
 -    def _get_posts_count(self, cr, uid, ids, field_name, arg, context=None):
 -        return dict((tag_id, self.pool['forum.post'].search_count(cr, uid, [('tag_ids', 'in', tag_id)], context=context)) for tag_id in ids)
 -
 -    def _get_tag_from_post(self, cr, uid, ids, context=None):
 -        return list(set(
 -            [tag.id for post in self.pool['forum.post'].browse(cr, SUPERUSER_ID, ids, context=context) for tag in post.tag_ids]
 -        ))
 -
 -    _columns = {
 -        'name': fields.char('Name', required=True),
 -        'forum_id': fields.many2one('forum.forum', 'Forum', required=True),
 -        'post_ids': fields.many2many('forum.post', 'forum_tag_rel', 'tag_id', 'post_id', 'Posts'),
 -        'posts_count': fields.function(
 -            _get_posts_count, type='integer', string="Number of Posts",
 -            store={
 -                'forum.post': (_get_tag_from_post, ['tag_ids'], 10),
 -            }
 -        ),
 -        'create_uid': fields.many2one('res.users', 'Created by', readonly=True),
 -    }
 +    name = fields.Char('Name', required=True)
 +    create_uid = fields.Many2one('res.users', string='Created by', readonly=True)
 +    forum_id = fields.Many2one('forum.forum', string='Forum', required=True)
 +    post_ids = fields.Many2many('forum.post', 'forum_tag_rel', 'forum_tag_id', 'forum_id', string='Posts')
 +    posts_count = fields.Integer('Number of Posts', compute='_get_posts_count', store=True)
 +
 +    @api.multi
 +    @api.depends("post_ids.tag_ids")
 +    def _get_posts_count(self):
 +        for tag in self:
 +            tag.posts_count = len(tag.post_ids)
@@@ -1,5 -1,4 +1,5 @@@
      openerp.website.if_dom_contains('.website_forum', function () {
 +        $("[data-toggle='popover']").popover();
          $('.karma_required').on('click', function (ev) {
              var karma = $(ev.currentTarget).data('karma');
              if (karma) {
          });
  
  
 +        $('.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. Example: http://www.odoo.com'+
 +                    '</div>');
 +                $link.parent().append($warning);
 +                $("button#btn_post_your_article")[0].disabled = true;
 +            } else {
 +                openerp.jsonRpc("/forum/get_url_title", 'call', {'url': $link.attr("value")}).then(function (data) {
 +                    $("input[name='post_name']")[0].value = data;
 +                    $('button#btn_post_your_article').prop('disabled', false);
 +                });
 +            }
 +        });
 +
          $('input.js_select2').select2({
              tags: true,
              tokenSeparators: [",", " ", "_"],
                              isNew: true,
                          };
                      }
 -                    
                  }
              },
              formatResult: function(term) {
                  data: function(term, page) {
                      return {
                          q: term,
 -                        t: 'select2',
                          l: 50
                      };
                  },
              // Take default tags from the input value
              initSelection: function (element, callback) {
                  var data = [];
-                 _.each(JSON.parse(element.val()), function(x) {
+                 _.each(element.data('init-value'), function(x) {
                      data.push({ id: x.id, text: x.name, isNew: false });
                  });
                  element.val('');
              },
          });
  
 -        if($('input.load_tags').length){
 -            var tags = $("input.load_tags").val();
 -            $("input.load_tags").val("");
 -            set_tags(tags);
 -        };
 -
 -        function set_tags(tags) {
 -            $("input.load_tags").textext({
 -                plugins: 'tags focus autocomplete ajax',
 -                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!'},
 -                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) {
 +            $('textarea.load_editor').each(function () {
 +                if (this['id']) {
 +                    CKEDITOR.replace(this['id']).on('instanceReady', CKEDITORLoadComplete);
                  }
              });
          }
-         function IsKarmaValid(eventNumber,minKarma){
-             "use strict";
-             if(parseInt($("#karma").val()) >= minKarma){
-                 CKEDITOR.tools.callFunction(eventNumber,this);
-                 return false;
-             } else {
-                 alert("Sorry you need more than " + minKarma + " Karma.");
-             }
-         }
 -        //END-TODO Remove in master
 -
 -        if ($('textarea.load_editor').length) {
 -            var editor = CKEDITOR.instances['content'];
 -            editor.on('instanceReady', CKEDITORLoadComplete);
 -        }
 -
++        
          function CKEDITORLoadComplete(){
              "use strict";
-             $('.cke_button__link').on('click', function() { IsKarmaValid(33,30); });
-             $('.cke_button__unlink').on('click', function() { IsKarmaValid(37,30); });
-             $('.cke_button__image').on('click', function() { IsKarmaValid(41,30); });
+             $('.cke_button__link').attr('onclick','website_forum_IsKarmaValid(33,30)');
+             $('.cke_button__unlink').attr('onclick','website_forum_IsKarmaValid(37,30)');
+             $('.cke_button__image').attr('onclick','website_forum_IsKarmaValid(41,30)');
          }
      });
  
+    function website_forum_IsKarmaValid(eventNumber, minKarma){
+         "use strict";
+         if(parseInt($("#karma").val()) >= minKarma){
+             CKEDITOR.tools.callFunction(eventNumber, this);
+             return false;
+         } else {
+             alert("Sorry you need more than " + minKarma + " Karma.");
+         }
+     }
@@@ -2,32 -2,6 +2,32 @@@
  <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_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>
  
 -<template id="assets_frontend" inherit_id="website.assets_frontend" name="website_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="/web/static/lib/jquery.textext/jquery.textext.js"/>
+         <script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
+         <link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
+         <link rel="stylesheet" href="/website/static/lib/select2-bootstrap-css/select2-bootstrap.css"/>
+         <script type="text/javascript" src="/website_forum/static/src/js/website_forum.js"/>
  <!-- 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">
 +                <h1 class="mt0">Welcome!</h1>
 +                <div t-field="forum.description"/>
 +                <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>
  
          <div id="wrap" class="container">
 -            <div class="row">
 +            <div class="row mb16">
                  <div class="col-sm-9">
                      <div t-foreach="notifications or []" t-as="notification" class="alert alert-success alert-dismissable">
                          <button type="button" class="close notification_close" t-att-id="notification.id" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
                      <t t-raw="0"/>
                  </div>
                  <div class="col-sm-3" id="right-column">
 -                    <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)}/ask?post_type=#{forum.default_post_type}">
 +                            <t t-if="forum.default_post_type == 'question'">Ask a Question</t>
 +                            <t t-if="forum.default_post_type == 'link'">Submit a Link</t>
 +                            <t t-if="forum.default_post_type == '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_question"><a t-attf-href="/forum/#{slug(forum)}/ask?post_type=question">Ask a Question</a></li>
 +                            <li t-if="forum.allow_link"><a t-attf-href="/forum/#{slug(forum)}/ask?post_type=link">Submit a Link</a></li>
 +                            <li t-if="forum.allow_discussion"><a t-attf-href="/forum/#{slug(forum)}/ask?post_type=discussion">New 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"/>
  
  <!-- Display a post -->
  <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>
 +    <div class="question">
 +        <div class="pull-left">
 +            <t t-call="website_forum.vote">
 +                <t t-set="post" t-value="question"/>
 +            </t>
          </div>
 -        <div class="col-md-10 clearfix">
 +        <div class="question-block">
              <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.post_type == 'link'">
 +                    <a t-att-href="question.content_link" t-raw="question.name" target="_blank"/>
 +                </t>
 +                <t t-if="question.post_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>
  <!-- Display a post as an answer -->
  <template id="display_post_answer">
      <div class="clearfix">
 -        <div t-attf-class="pull-left text-center mb16 box #{len(answer.vote_ids) and 'oe_green' or 'oe_grey'}">
 +        <div t-attf-class="pull-left text-center mb16 #{len(answer.vote_ids) and 'oe_green' or 'oe_grey'}">
              <div t-esc="len(answer.vote_ids)"/>
          </div>
          <div class="question-name" style="margin-left: 32px;">
  <!-- 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 not in ('link','question','discussion')">Posts</span>
 +                <span t-if="post_type == 'question'">Questions</span>
 +                <span t-if="post_type == 'link'">Links</span>
 +                <span t-if="post_type == 'discussion'">Threads</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">
                    <li t-if="uid" t-att-class="filters == 'followed' and 'active' or '' ">
                        <a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', filters='followed')">Followed</a>
                    </li>
 -                  <li class="dropdown-header">Tags</li>
 +                  <li t-if="tag" class="dropdown-header">Tags</li>
                    <li t-if="tag" t-att-class="tag and 'active' or '' ">
                        <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="new_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, around 20% of the submited
 +            posts will be featured.
 +        </p>
 +        <form t-attf-action="/forum/#{ slug(forum) }/new?post_type=link" 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="content_link" 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="post_name" 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="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
 +                    <input type="text" name="post_tags" class="form-control js_select2"/>
 +                </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</button>
 +                </div>
 +            </div>
 +        </form>
 +    </t>
 +</template>
 +
 +<!-- Edition: Post your Discussion Topic -->
 +<template id="new_discussion">
 +    <t t-call="website_forum.header">
 +        <h1 class="mt0">New Topic</h1>
 +        <p>
 +            <b>Share</b> Something Awesome.
 +        </p>
 +        <form t-attf-action="/forum/#{slug(forum)}/new?post_type=discussion" 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="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
 +            <input type="text" name="post_tags" placeholder="Tags" class="form-control js_select2"/>
 +            <br/><br/>
 +            <button class="btn btn-primary">Post Your Topic</button>
 +        </form>
 +    </t>
 +</template>
 +
  <!-- Edition: ask your question -->
 -<template id="ask_question">
 +<template id="new_question">
      <t t-call="website_forum.header">
 -        <h1 class="mt0">Ask your Question</h1>
 +        <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>
 +        <form t-attf-action="/forum/#{slug(forum)}/new?post_type=question" 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 Question Title..."/>
              <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
 -            <input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
 -            <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="hidden" name="tag_type" value="select2"/>
 -            <input type="hidden" name="question_tags" placeholder="Tags" class="form-control js_select2"/>
 -            <br/>
 +            <input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
 +            <input type="hidden" name="post_tags" placeholder="Tags" class="form-control js_select2"/>
 +            <br/><br/>
              <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>
 +                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.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.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"/>
              <input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
 -            <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="hidden" name="tag_type" value="select2"/>
 -                <input type="hidden" name="question_tag" class="form-control col-md-9 js_select2" placeholder="Tags" value="see data init value" t-attf-data-init-value="#{tags}"/>
 +                <input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
-                 <input type="text" name="post_tag" class="form-control col-md-9 js_select2" placeholder="Tags" t-attf-value="#{tags}"/>
++                <input type="text" name="post_tag" class="form-control col-md-9 js_select2" placeholder="Tags" t-attf-data-init-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="mt8">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">Post Comment</button>
 +        </form>
 +    </div>
 +</template>
 +
  <!-- Edition: post an answer -->
  <template id="post_answer">
 -    <h3 class="mt10">Your answer</h3>
 -    <p>
 +    <h3 class="mt8">Your Answer</h3>
 +    <p t-if="question.post_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>
 +            t-att-data-karma="question.forum_id.karma_answer">Post Answer</button>
      </form>
  </template>
  
  <template id="vote">
 -    <div t-attf-class="box oe_grey">
 -        <a t-attf-class="vote_up fa fa-thumbs-up no-decoration #{post.user_vote == 1 and 'text-success' or ''} #{((post.user_vote == 1 and not post.can_downvote) or not post.can_upvote) and 'karma_required' or ''}"
 -            t-attf-data-karma="#{post.user_vote == 1 and post.karma_downvote or post.karma_upvote}"
 +    <div class="vote text-center">
 +        <a t-attf-class="vote_up fa fa-caret-up no-decoration #{post.user_vote == 1 and 'text-success' or ''} #{((post.user_vote == 1 and not post.can_downvote) or not post.can_upvote) and 'karma_required' or ''}"
 +            t-attf-data-karma="#{post.user_vote == 1 and post.forum_id.karma_downvote or post.forum_id.karma_upvote}"
              t-attf-data-href="/forum/#{slug(post.forum_id)}/post/#{slug(post)}/upvote"/>
 -        <span id="vote_count" t-esc="post.vote_count"/>
 -        <a t-attf-class="vote_down fa fa-thumbs-down no-decoration #{post.user_vote == -1 and 'text-warning' or ''} #{((post.user_vote == -1 and not post.can_upvote) or not post.can_downvote) and 'karma_required' or ''}"
 -            t-attf-data-karma="#{post.user_vote == -1 and post.karma_upvote or post.karma_downvote}"
 +        <div class="vote_count text-muted" t-esc="post.vote_count"/>
 +        <a t-attf-class="vote_down fa fa-caret-down no-decoration #{post.user_vote == -1 and 'text-warning' or ''} #{((post.user_vote == -1 and not post.can_upvote) or not post.can_downvote) and 'karma_required' or ''}"
 +            t-attf-data-karma="#{post.user_vote == -1 and post.forum_id.karma_upvote or post.forum_id.karma_downvote}"
              t-attf-data-href="/forum/#{slug(post.forum_id)}/post/#{slug(post)}/downvote"/>
 -        <div t-if="vote_count &gt; 1" class="subtitle">
 -            votes
 -        </div>
 -        <div t-if="vote_count &lt; 2" class="subtitle">
 -            vote
 -        </div>
 +        <t t-raw="0"/>
      </div>
  </template>
  
  <!-- 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 class="col-md-2 hidden-xs text-center">
 +        <div t-attf-class="question">
 +            <div class="pull-left">
                  <t t-call="website_forum.vote">
                      <t t-set="post" t-value="question"/>
 +                    <div class="mt4">
 +                        <a t-attf-data-href="/forum/#{slug(question.forum_id)}/question/#{slug(question)}/toggle_favourite"
 +                            t-attf-class="favourite_question no-decoration fa fa-2x fa-star #{question.user_favourite and 'forum_favourite_question' or ''}"/>
 +                    </div>
                  </t>
 -                <div class="text-muted text-center">
 -                    <span t-field="question.views"/> Views
 -                </div>
 -                <div class="mt4">
 -                    <a t-attf-data-href="/forum/#{slug(question.forum_id)}/question/#{slug(question)}/toggle_favourite"
 -                        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">
 -                <h1 class="mt0">
 -                    <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
 +            <div t-attf-class="question-block #{not question.active and 'alert alert-danger' or ''}">
 +                <h1 class="mt0 mb0">
 +                    <t t-if="question.post_type == 'link'">
 +                        <a t-att-href="question.content_link" t-raw="question.name"/>
 +                    </t>
 +                    <t t-if="question.post_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="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="label label-default" t-field="tag.name"/>
 +                        </t>
 +                    </div>
 +                </div>
 +                <div class="text-muted mb16">
 +                    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.create_date" t-field-options='{"format":"short"}'/>
 +                    • <span t-field="question.views"/> views
 +                </div>
 +
                  <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.post_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 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"/>
 +                <div t-if="question.post_type != 'link' and question.create_uid.karma &gt;= forum.karma_dofollow"><span t-field="question.content" class="oe_no_empty"/></div>
 +                <div t-if="question.post_type != 'link' and question.create_uid.karma &lt; forum.karma_dofollow"><span t-field="question.content" class="oe_no_empty" t-field-options='{"nofollow": 1}'/></div>
 +
 +                <div class="clearfix">
 +                    <ul class="pull-right list-inline mb0" id="options">
 +                        <li t-if="question.state != 'close'">
 +                            <t t-call="website_forum.link_button">
 +                                <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/ask_for_close'"/>
 +                                <t t-set="label" t-value="'Close'"/>
 +                                <t t-set="classes" t-value="'fa-times'"/>
 +                                <t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
                              </t>
 -                        </div>
 -                        <ul class="list-inline" id="options">
 -                            <li>
 -                                <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}"
 -                                    t-attf-data-target="#comment#{ question._name.replace('.','') + '-' + str(question.id) }">
 -                                    Comment
 -                                </a>
 -                            </li>
 -                            <li t-if="question.state != 'close'">
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/ask_for_close'"/>
 -                                    <t t-set="label" t-value="'Close'"/>
 -                                    <t t-set="classes" t-value="'fa-times'"/>
 -                                    <t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
 -                                </t>
 -                            </li>
 -                            <li t-if="question.state == 'close'">
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/reopen'"/>
 -                                    <t t-set="label" t-value="'Reopen'"/>
 -                                    <t t-set="classes" t-value="'fa-undo'"/>
 -                                    <t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
 -                                </t>
 -                            </li>
 -                            <li>
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) +'/post/' + slug(question) + '/edition'"/>
 -                                    <t t-set="label" t-value="'Edit'"/>
 -                                    <t t-set="classes" t-value="'fa-edit'"/>
 -                                    <t t-set="karma" t-value="not question.can_edit and question.karma_edit or 0"/>
 -                                </t>
 -                            </li>
 -                            <li t-if="question.active">
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/delete'"/>
 -                                    <t t-set="label" t-value="'Delete'"/>
 -                                    <t t-set="classes" t-value="'fa-trash-o'"/>
 -                                    <t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
 -                                </t>
 -                            </li>
 -                            <li t-if="not question.active">
 -                                <t t-call="website_forum.link_button">
 -                                    <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/undelete'"/>
 -                                    <t t-set="label" t-value="'Undelete'"/>
 -                                    <t t-set="classes" t-value="'fa-trash-o'"/>
 -                                    <t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
 -                                </t>
 -                            </li>
 -                        </ul>
 -                    </div>
 -                    <div>
 -                        <img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{question.create_uid.id}/avatar"/>
 -                        <div>
 -                            <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;"/>
 -                            <div t-field="question.create_uid" t-field-options='{"widget": "contact", "badges": true, "fields": ["karma"]}'/>
 -                            <span class="text-muted">Asked on <span t-field="question.create_date" t-field-options='{"format":"short"}'/></span>
 -                        </div>
 -                    </div>
 -                    <div class="visible-xs text-center">
 -                        <t t-call="website_forum.vote">
 -                            <t t-set="post" t-value="question"/>
 -                        </t>
 -                        <div class="text-muted text-center">
 -                            <span t-field="question.views"/> Views
 -                        </div>
 -                        <div class="mt4">
 -                            <a t-attf-data-href="/forum/#{slug(question.forum_id)}/question/#{slug(question)}/toggle_favourite"
 -                                t-attf-class="favourite_question no-decoration fa fa-2x fa-star #{question.user_favourite and 'forum_favourite_question' or ''}"/>
 -                        </div>
 -                    </div>
 +                        </li>
 +                        <li t-if="question.state == 'close'">
 +                            <t t-call="website_forum.link_button">
 +                                <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/reopen'"/>
 +                                <t t-set="label" t-value="'Reopen'"/>
 +                                <t t-set="classes" t-value="'fa-undo'"/>
 +                                <t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
 +                            </t>
 +                        </li>
 +                        <li>
 +                            <t t-call="website_forum.link_button">
 +                                <t t-set="url" t-value="'/forum/' + slug(forum) +'/post/' + slug(question) + '/edit'"/>
 +                                <t t-set="label" t-value="'Edit'"/>
 +                                <t t-set="classes" t-value="'fa-edit'"/>
 +                                <t t-set="karma" t-value="not question.can_edit and question.karma_edit or 0"/>
 +                            </t>
 +                        </li>
 +                        <li t-if="question.active">
 +                            <t t-call="website_forum.link_button">
 +                                <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/delete'"/>
 +                                <t t-set="label" t-value="'Delete'"/>
 +                                <t t-set="classes" t-value="'fa-trash-o'"/>
 +                                <t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
 +                            </t>
 +                        </li>
 +                        <li t-if="not question.active">
 +                            <t t-call="website_forum.link_button">
 +                                <t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/undelete'"/>
 +                                <t t-set="label" t-value="'Undelete'"/>
 +                                <t t-set="classes" t-value="'fa-trash-o'"/>
 +                                <t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
 +                            </t>
 +                        </li>
 +                    </ul>
 +                    <ul class="list-inline mb0">
 +                        <li t-if="question.post_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}"
 +                                t-attf-data-target="#comment#{ question._name.replace('.','') + '-' + str(question.id) }">
 +                                Comment
 +                            </a>
 +                        </li>
 +                    </ul>
                  </div>
 +
                  <t t-call="website_forum.post_comment">
                      <t t-set="object" t-value="question"/>
                  </t>
              </div>
          </div>
 -        <hr/>
 -
 -        <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">
 -                    <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 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>
 +        <div t-foreach="question.child_ids" t-as="post_answer" class="mt16 mb32">
 +            <hr class="mb4 mt4"/>
 +            <t t-call="website_forum.post_answers">
 +                <t t-set="answer" t-value="post_answer"/>
 +            </t>
          </div>
 -        <div t-if="not question.uid_has_answered">
 +        <div t-if="question.post_type != 'question' or question.post_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.uid_has_answered" class="mb16">
 +        <div t-if="question.post_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>
  
 +<template id="post_answers">
 +    <a t-attf-id="answer-#{str(answer.id)}"/>
 +    <div t-attf-class="forum_answer" t-attf-id="answer_#{answer.id}" >
 +        <div class="pull-left">
 +            <t t-call="website_forum.vote">
 +                <t t-set="post" t-value="answer"/>
 +                <div t-if="question.post_type == 'question'" class="mt4">
 +                    <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>
 +            </t>
 +        </div>
 +        <div class="question-block">
 +            <div class="pull-right author-box">
 +                <img class="pull-left img img-rounded img-avatar" t-attf-src="/forum/user/#{user.id}/avatar"/>
 +                <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 class="text-muted">On <span t-field="answer.create_date" t-field-options='{"format":"short"}'/></div>
 +            </div>
 +            <div class="clearfix" t-if="answer.create_uid.karma &gt;= forum.karma_dofollow"><span t-field="answer.content" class="oe_no_empty"/></div>
 +            <div class="clearfix" t-if="answer.create_uid.karma &lt; forum.karma_dofollow"><span t-field="answer.content" class="oe_no_empty" t-field-options='{"nofollow": 1}'/></div>
 +            <div class="clearfix">
 +                <ul class="list-inline pull-right mb0">
 +                    <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.post_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>
 +                <ul class="list-inline mb0">
 +                    <li t-if="question.post_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.post_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" data-toggle="collapse"
 +                            t-attf-data-target="#reply#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Reply
 +                        </a>
 +                    </li>
 +                </ul>
 +
 +            </div>
 +            <hr class="mt4 mb4"/>
 +
 +            <t t-if="answer.post_type == 'question'" t-call="website_forum.post_comment">
 +                <t t-set="object" t-value="answer"/>
 +            </t>
 +            <div t-if="answer.post_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="mt4 mb4">
 +                <t t-call="website_forum.post_answers">
 +                    <t t-set="answer" t-value="child_answer"/>
 +                </t>
 +            </div>
 +        </div>
 +    </div>
 +</template>
 +
  <!-- Utility template: Post a Comment -->
  <template id="post_comment">
      <div class="row clearfix">
                          <t t-set="classes" t-value="'close comment_delete fa-times'"/>
                      </t>
  
 -                    <span t-field="message.body"/>
 +                    <div t-if="message.create_uid.karma &gt;= forum.karma_dofollow"><span t-field="message.body" class="oe_no_empty"/></div>
 +                    <div t-if="message.create_uid.karma &lt; forum.karma_dofollow"><span t-field="message.body" class="oe_no_empty" t-field-options='{"nofollow": 1}'/></div>
                      <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>
                  </tr>
                  </table>
                  <div class="well well-sm">
 -                    <span t-field="user.partner_id.website_description" class="oe_no_empty"/>
 +                    <div t-if="user.karma &gt;= forum.karma_dofollow"><span t-field="user.partner_id.website_description" class="oe_no_empty"/></div>
 +                    <div t-if="user.karma &lt; forum.karma_dofollow"><span t-field="user.partner_id.website_description" class="oe_no_empty" t-field-options='{"nofollow": 1}'/></div>
                      <t t-if="uid == user.id">
                          <a class="fa fa-arrow-right" t-attf-href="/forum/#{slug(forum)}/user/#{slug(user)}/edit"> Edit Your Bio</a>
                      </t>
@@@ -2,18 -2,12 +2,18 @@@
  <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">
          <div id="wrap" class="container" t-ignore="True">
              <div id="email_template" class="mb32" t-att-style="mode != 'email_template' and 'display: none' or ''">
-                 <a class="mt16 btn btn-default pull-right" 
+                 <a class="mt16 btn btn-default pull-right css_editable_mode_hidden" 
                    t-attf-href="/web#return_label=Website&amp;model=#{model}&amp;id=#{res_id}&amp;view_type=form">
                      Back
                  </a>
@@@ -38,9 -32,9 +38,9 @@@
                  </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 css_editable_mode_hidden" 
 +                <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
+                     Back to the mass mailing
                  </a>
                  <h1 class="page-header mt16">
                      Design Your Email
@@@ -126,9 -126,10 +126,10 @@@ class website_sale(http.Controller)
              currency_id = self.get_pricelist().currency_id.id
              for p in product.product_variant_ids:
                  price = currency_obj.compute(cr, uid, website_currency_id, currency_id, p.lst_price)
-                 attribute_value_ids.append([p.id, map(int, p.attribute_value_ids), p.price, price])
+                 attribute_value_ids.append([p.id, [v.id for v in p.attribute_value_ids if len(v.attribute_id.value_ids) > 1], p.price, price])
          else:
-             attribute_value_ids = [[p.id, map(int, p.attribute_value_ids), p.price, p.lst_price] for p in product.product_variant_ids]
+             attribute_value_ids = [[p.id, [v.id for v in p.attribute_value_ids if len(v.attribute_id.value_ids) > 1], p.price, p.lst_price]
+                 for p in product.product_variant_ids]
  
          return attribute_value_ids
  
              domain += ['|', '|', '|', ('name', 'ilike', search), ('description', 'ilike', search),
                  ('description_sale', 'ilike', search), ('product_variant_ids.default_code', 'ilike', search)]
          if category:
-             domain += [('product_variant_ids.public_categ_ids', 'child_of', int(category))]
+             domain += [('public_categ_ids', 'child_of', int(category))]
  
          attrib_list = request.httprequest.args.getlist('attrib')
          attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
          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:
              category = pool['product.public.category'].browse(cr, uid, int(category), context=context)
              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, 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, 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)
                  }
              else:
                  state = 'done'
 -                message = ""
 +                message = '<h2>%s</h2>' % _('Your order has been confirmed, thank you for your loyalty.')
                  validation = None
          else:
              tx = request.registry['payment.transaction'].browse(cr, SUPERUSER_ID, tx_ids[0], context=context)
              state = tx.state
 +            message = '<h2>%s</h2>' % _('Thank you for your order.')
              if state == 'done':
 -                message = '<p>%s</p>' % _('Your payment has been received.')
 +                message += '<p>%s</p>' % _('Your payment has been received.')
              elif state == 'cancel':
 -                message = '<p>%s</p>' % _('The payment seems to have been canceled.')
 +                message += '<p>%s</p>' % _('The payment seems to have been canceled.')
              elif state == 'pending' and tx.acquirer_id.validation == 'manual':
 -                message = '<p>%s</p>' % _('Your transaction is waiting confirmation.')
 +                message += '<p>%s</p>' % _('Your transaction is waiting confirmation.')
                  if tx.acquirer_id.post_msg:
                      message += tx.acquirer_id.post_msg
              else:
 -                message = '<p>%s</p>' % _('Your transaction is waiting confirmation.')
 +                message += '<p>%s</p>' % _('Your transaction is waiting confirmation.')
              validation = tx.acquirer_id.validation
  
          return {
@@@ -22,8 -22,8 +22,8 @@@ class sale_order(osv.Model)
              help='Order Lines to be displayed on the website. They should not be used for computation purpose.',
          ),
          'cart_quantity': fields.function(_cart_qty, type='integer', string='Cart Quantity'),
-         'payment_acquirer_id': fields.many2one('payment.acquirer', 'Payment Acquirer', on_delete='set null'),
-         'payment_tx_id': fields.many2one('payment.transaction', 'Transaction', on_delete='set null'),
+         'payment_acquirer_id': fields.many2one('payment.acquirer', 'Payment Acquirer', on_delete='set null', copy=False),
+         'payment_tx_id': fields.many2one('payment.transaction', 'Transaction', on_delete='set null', copy=False),
      }
  
      def _get_errors(self, cr, uid, order, context=None):
@@@ -136,7 -136,7 +136,7 @@@ class website(orm.Model)
                      'user_id': w.user_id.id,
                      'partner_id': partner.id,
                      'pricelist_id': partner.property_product_pricelist.id,
 -                    'section_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'website', 'salesteam_website_sales')[1],
 +                    'team_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'website', 'salesteam_website_sales')[1],
                  }
                  sale_order_id = sale_order_obj.create(cr, SUPERUSER_ID, values, context=context)
                  values = sale_order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner.id, context=context)['value']
                      pricelist_id = pricelist_ids[0]
                      request.session['sale_order_code_pricelist_id'] = pricelist_id
                      update_pricelist = True
-                 request.session['sale_order_code_pricelist_id'] = False
  
              pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
  
diff --combined doc/conf.py
@@@ -46,16 -46,16 +46,16 @@@ master_doc = 'index
  
  # General information about the project.
  project = u'odoo'
- copyright = u'OpenERP S.A.'
+ copyright = u'Odoo S.A.'
  
  # The version info for the project you're documenting, acts as replacement for
  # |version| and |release|, also used in various other places throughout the
  # built documents.
  #
  # The short X.Y version.
 -version = '8.0'
 +version = 'master'
  # The full version, including alpha/beta/rc tags.
 -release = '8.0'
 +release = 'master'
  
  # There are two options for replacing |today|: either, you set today to some
  # non-false value, then it is used:
diff --combined doc/reference.rst
@@@ -18,6 -18,6 +18,7 @@@ Referenc
      reference/qweb
      reference/javascript
  
+     reference/translations
      reference/reports
      reference/workflows
 +    reference/cdn
@@@ -1,6 -1,5 +1,6 @@@
  # -*- coding: utf-8 -*-
  import collections
 +import copy
  import cStringIO
  import datetime
  import hashlib
@@@ -29,7 -28,7 +29,7 @@@ import openerp.tools.lr
  from openerp.http import request
  from openerp.tools.safe_eval import safe_eval as eval
  from openerp.osv import osv, orm, fields
 -from openerp.tools import html_escape as escape
 +from openerp.tools import html_escape as escape, which
  from openerp.tools.translate import _
  
  _logger = logging.getLogger(__name__)
@@@ -62,31 -61,12 +62,31 @@@ def raise_qweb_exception(etype=None, **
          e.qweb['cause'] = original
          raise
  
 +class FileSystemLoader(object):
 +    def __init__(self, path):
 +        # TODO: support multiple files #add_file() + add cache
 +        self.path = path
 +        self.doc = etree.parse(path).getroot()
 +
 +    def __iter__(self):
 +        for node in self.doc:
 +            name = node.get('t-name')
 +            if name:
 +                yield name
 +
 +    def __call__(self, name):
 +        for node in self.doc:
 +            if node.get('t-name') == name:
 +                root = etree.Element('templates')
 +                root.append(copy.deepcopy(node))
 +                arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
 +                return arch
 +
  class QWebContext(dict):
 -    def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
 +    def __init__(self, cr, uid, data, loader=None, context=None):
          self.cr = cr
          self.uid = uid
          self.loader = loader
 -        self.templates = templates or {}
          self.context = context
          dic = dict(data)
          super(QWebContext, self).__init__(dic)
          """
          return QWebContext(self.cr, self.uid, dict.copy(self),
                             loader=self.loader,
 -                           templates=self.templates,
                             context=self.context)
  
      def __copy__(self):
@@@ -161,24 -142,50 +161,24 @@@ class QWeb(orm.AbstractModel)
      def register_tag(self, tag, func):
          self._render_tag[tag] = func
  
 -    def add_template(self, qwebcontext, name, node):
 -        """Add a parsed template in the context. Used to preprocess templates."""
 -        qwebcontext.templates[name] = node
 -
 -    def load_document(self, document, res_id, qwebcontext):
 -        """
 -        Loads an XML document and installs any contained template in the engine
 +    def get_template(self, name, qwebcontext):
 +        origin_template = qwebcontext.get('__caller__') or qwebcontext['__stack__'][0]
 +        try:
 +            document = qwebcontext.loader(name)
 +        except ValueError:
 +            raise_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
  
 -        :type document: a parsed lxml.etree element, an unparsed XML document
 -                        (as a string) or the path of an XML file to load
 -        """
 -        if not isinstance(document, basestring):
 -            # assume lxml.etree.Element
 +        if hasattr(document, 'documentElement'):
              dom = document
          elif document.startswith("<?xml"):
              dom = etree.fromstring(document)
          else:
              dom = etree.parse(document).getroot()
  
 +        res_id = isinstance(name, (int, long)) and name or None
          for node in dom:
 -            if node.get('t-name'):
 -                name = str(node.get("t-name"))
 -                self.add_template(qwebcontext, name, node)
 -            if res_id and node.tag == "t":
 -                self.add_template(qwebcontext, res_id, node)
 -                res_id = None
 -
 -    def get_template(self, name, qwebcontext):
 -        """ Tries to fetch the template ``name``, either gets it from the
 -        context's template cache or loads one with the context's loader (if
 -        any).
 -
 -        :raises QWebTemplateNotFound: if the template can not be found or loaded
 -        """
 -        origin_template = qwebcontext.get('__caller__') or qwebcontext['__stack__'][0]
 -        if qwebcontext.loader and name not in qwebcontext.templates:
 -            try:
 -                xml_doc = qwebcontext.loader(name)
 -            except ValueError:
 -                raise_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
 -            self.load_document(xml_doc, isinstance(name, (int, long)) and name or None, qwebcontext=qwebcontext)
 -
 -        if name in qwebcontext.templates:
 -            return qwebcontext.templates[name]
 +            if node.get('t-name') or (res_id and node.tag == "t"):
 +                return node
  
          raise QWebTemplateNotFound("Template %r not found" % name, template=origin_template)
  
          generated_attributes = ""
          t_render = None
          template_attributes = {}
 +
 +        debugger = element.get('t-debug')
 +        if debugger is not None:
 +            if openerp.tools.config['dev_mode']:
 +                __import__(debugger).set_trace()  # pdb, ipdb, pudb, ...
 +            else:
 +                _logger.warning("@t-debug in template '%s' is only available in --dev mode" % qwebcontext['__template__'])
 +
          for (attribute_name, attribute_value) in element.attrib.iteritems():
              attribute_name = str(attribute_name)
              if attribute_name == "groups":
              else:
                  generated_attributes += self.render_attribute(element, attribute_name, attribute_value, qwebcontext)
  
 -        if 'debug' in template_attributes:
 -            debugger = template_attributes.get('debug', 'pdb')
 -            __import__(debugger).set_trace()  # pdb, ipdb, pudb, ...
          if t_render:
              result = self._render_tag[t_render](self, element, template_attributes, generated_attributes, qwebcontext)
          else:
              result = self.render_element(element, template_attributes, generated_attributes, qwebcontext)
  
          if element.tail:
 -            result += element.tail.encode('utf-8')
 +            result += self.render_tail(element.tail, element, qwebcontext)
  
          if isinstance(result, unicode):
              return result.encode('utf-8')
          if inner:
              g_inner = inner.encode('utf-8') if isinstance(inner, unicode) else inner
          else:
 -            g_inner = [] if element.text is None else [element.text.encode('utf-8')]
 +            g_inner = [] if element.text is None else [self.render_text(element.text, element, qwebcontext)]
              for current_node in element.iterchildren(tag=etree.Element):
                  try:
                      g_inner.append(self.render_node(current_node, qwebcontext))
      def render_attribute(self, element, name, value, qwebcontext):
          return ' %s="%s"' % (name, escape(value))
  
 +    def render_text(self, text, element, qwebcontext):
 +        return text.encode('utf-8')
 +
 +    def render_tail(self, tail, element, qwebcontext):
 +        return tail.encode('utf-8')
 +
      # Attributes
      def render_att_att(self, element, attribute_name, attribute_value, qwebcontext):
          if attribute_name.startswith("t-attf-"):
@@@ -822,7 -818,7 +822,7 @@@ class MonetaryConverter(osv.AbstractMod
          # The log10 of the rounding should be the number of digits involved if
          # negative, if positive clamp to 0 digits and call it a day.
          # nb: int() ~ floor(), we want nearest rounding instead
-         precision = int(round(math.log10(display_currency.rounding)))
+         precision = int(math.floor(math.log10(display_currency.rounding)))
          fmt = "%.{0}f".format(-precision if precision < 0 else 0)
  
          from_amount = record[field_name]
@@@ -939,7 -935,7 +939,7 @@@ class Contact(orm.AbstractModel)
  
          val = {
              'name': value.split("\n")[0],
 -            'address': escape("\n".join(value.split("\n")[1:])),
 +            'address': escape("\n".join(value.split("\n")[1:])).strip(),
              'phone': value_rec.phone,
              'mobile': value_rec.mobile,
              'fax': value_rec.fax,
@@@ -1056,8 -1052,17 +1056,8 @@@ class AssetNotFound(AssetError)
      pass
  
  class AssetsBundle(object):
 -    # Sass installation:
 -    #
 -    #       sudo gem install sass compass bootstrap-sass
 -    #
 -    # If the following error is encountered:
 -    #       'ERROR: Cannot load compass.'
 -    # Use this:
 -    #       sudo gem install compass --pre
 -    cmd_sass = ['sass', '--stdin', '-t', 'compressed', '--unix-newlines', '--compass', '-r', 'bootstrap-sass']
      rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
 -    rx_sass_import = re.compile("""(@import\s?['"]([^'"]+)['"])""")
 +    rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
      rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
  
      def __init__(self, xmlid, debug=False, cr=None, uid=None, context=None, registry=None):
                  media = el.get('media')
                  if el.tag == 'style':
                      if atype == 'text/sass' or src.endswith('.sass'):
 -                        self.stylesheets.append(SassAsset(self, inline=el.text, media=media))
 +                        self.stylesheets.append(SassStylesheetAsset(self, inline=el.text, media=media))
 +                    elif atype == 'text/less' or src.endswith('.less'):
 +                        self.stylesheets.append(LessStylesheetAsset(self, inline=el.text, media=media))
                      else:
                          self.stylesheets.append(StylesheetAsset(self, inline=el.text, media=media))
                  elif el.tag == 'link' and el.get('rel') == 'stylesheet' and self.can_aggregate(href):
                      if href.endswith('.sass') or atype == 'text/sass':
 -                        self.stylesheets.append(SassAsset(self, url=href, media=media))
 +                        self.stylesheets.append(SassStylesheetAsset(self, url=href, media=media))
 +                    elif href.endswith('.less') or atype == 'text/less':
 +                        self.stylesheets.append(LessStylesheetAsset(self, url=href, media=media))
                      else:
                          self.stylesheets.append(StylesheetAsset(self, url=href, media=media))
                  elif el.tag == 'script' and not src:
          response = []
          if debug:
              if css and self.stylesheets:
 -                self.compile_sass()
 +                self.preprocess_css()
 +                if self.css_errors:
 +                    msg = '\n'.join(self.css_errors)
 +                    self.stylesheets.append(StylesheetAsset(self, inline=self.css_message(msg)))
                  for style in self.stylesheets:
                      response.append(style.to_html())
              if js:
          return content
  
      def css(self):
 +        """Generate css content from given bundle"""
          content = self.get_cache('css')
          if content is None:
 -            self.compile_sass()
 -            content = '\n'.join(asset.minify() for asset in self.stylesheets)
 +            content = self.preprocess_css()
  
              if self.css_errors:
                  msg = '\n'.join(self.css_errors)
 -                content += self.css_message(msg.replace('\n', '\\A '))
 +                content += self.css_message(msg)
  
              # move up all @import rules to the top
              matches = []
  
      def set_cache(self, type, content):
          ira = self.registry['ir.attachment']
 -        url_prefix = '/web/%s/%s/' % (type, self.xmlid)
 -        # Invalidate previous caches
 -        oids = ira.search(self.cr, openerp.SUPERUSER_ID, [('url', '=like', url_prefix + '%')], context=self.context)
 -        if oids:
 -            ira.unlink(self.cr, openerp.SUPERUSER_ID, oids, context=self.context)
 -        url = url_prefix + self.version
 +        ira.invalidate_bundle(self.cr, openerp.SUPERUSER_ID, type=type, xmlid=self.xmlid)
 +        url = '/web/%s/%s/%s' % (type, self.xmlid, self.version)
          ira.create(self.cr, openerp.SUPERUSER_ID, dict(
                      datas=content.encode('utf8').encode('base64'),
                      type='binary',
                  ), context=self.context)
  
      def css_message(self, message):
 +        # '\A' == css content carriage return
 +        message = message.replace('\n', '\\A ').replace('"', '\\"')
          return """
              body:before {
                  background: #ffc;
                  white-space: pre;
                  content: "%s";
              }
 -        """ % message.replace('"', '\\"')
 +        """ % message
  
 -    def compile_sass(self):
 +    def preprocess_css(self):
          """
 -            Checks if the bundle contains any sass content, then compiles it to css.
 -            Css compilation is done at the bundle level and not in the assets
 -            because they are potentially interdependant.
 +            Checks if the bundle contains any sass/less content, then compiles it to css.
 +            Returns the bundle's flat css.
          """
 -        sass = [asset for asset in self.stylesheets if isinstance(asset, SassAsset)]
 -        if not sass:
 -            return
 -        source = '\n'.join([asset.get_source() for asset in sass])
 -
 -        # move up all @import rules to the top and exclude file imports
 +        for atype in (SassStylesheetAsset, LessStylesheetAsset):
 +            assets = [asset for asset in self.stylesheets if isinstance(asset, atype)]
 +            if assets:
 +                cmd = assets[0].get_command()
 +                source = '\n'.join([asset.get_source() for asset in assets])
 +                compiled = self.compile_css(cmd, source)
 +
 +                fragments = self.rx_css_split.split(compiled)
 +                at_rules = fragments.pop(0)
 +                if at_rules:
 +                    # Sass and less moves @at-rules to the top in order to stay css 2.1 compatible
 +                    self.stylesheets.insert(0, StylesheetAsset(self, inline=at_rules))
 +                while fragments:
 +                    asset_id = fragments.pop(0)
 +                    asset = next(asset for asset in self.stylesheets if asset.id == asset_id)
 +                    asset._content = fragments.pop(0)
 +
 +        return '\n'.join(asset.minify() for asset in self.stylesheets)
 +
 +    def compile_css(self, cmd, source):
 +        """Sanitizes @import rules, remove duplicates @import rules, then compile"""
          imports = []
 -        def push(matchobj):
 +        def sanitize(matchobj):
              ref = matchobj.group(2)
 -            line = '@import "%s"' % ref
 +            line = '@import "%s"%s' % (ref, matchobj.group(3))
              if '.' not in ref and line not in imports and not ref.startswith(('.', '/', '~')):
                  imports.append(line)
 +                return line
 +            msg = "Local import '%s' is forbidden for security reasons." % ref
 +            _logger.warning(msg)
 +            self.css_errors.append(msg)
              return ''
 -        source = re.sub(self.rx_sass_import, push, source)
 -        imports.append(source)
 -        source = u'\n'.join(imports)
 +        source = re.sub(self.rx_preprocess_imports, sanitize, source)
  
          try:
 -            compiler = Popen(self.cmd_sass, stdin=PIPE, stdout=PIPE, stderr=PIPE)
 +            compiler = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
          except Exception:
 -            msg = "Could not find 'sass' program needed to compile sass/scss files"
 +            msg = "Could not execute command %r" % cmd[0]
              _logger.error(msg)
              self.css_errors.append(msg)
 -            return
 +            return ''
          result = compiler.communicate(input=source.encode('utf-8'))
          if compiler.returncode:
 -            error = self.get_sass_error(''.join(result), source=source)
 +            error = self.get_preprocessor_error(''.join(result), source=source)
              _logger.warning(error)
              self.css_errors.append(error)
 -            return
 +            return ''
          compiled = result[0].strip().decode('utf8')
 -        fragments = self.rx_css_split.split(compiled)[1:]
 -        while fragments:
 -            asset_id = fragments.pop(0)
 -            asset = next(asset for asset in sass if asset.id == asset_id)
 -            asset._content = fragments.pop(0)
 -
 -    def get_sass_error(self, stderr, source=None):
 -        # TODO: try to find out which asset the error belongs to
 +        return compiled
 +
 +    def get_preprocessor_error(self, stderr, source=None):
 +        """Improve and remove sensitive information from sass/less compilator error messages"""
          error = stderr.split('Load paths')[0].replace('  Use --trace for backtrace.', '')
 +        if 'Cannot load compass' in error:
 +            error += "Maybe you should install the compass gem using this extra argument:\n\n" \
 +                     "    $ sudo gem install compass --pre\n"
          error += "This error occured while compiling the bundle '%s' containing:" % self.xmlid
          for asset in self.stylesheets:
 -            if isinstance(asset, SassAsset):
 +            if isinstance(asset, PreprocessedCSS):
                  error += '\n    - %s' % (asset.url if asset.url else '<inline sass>')
          return error
  
@@@ -1358,7 -1343,7 +1358,7 @@@ class WebAsset(object)
  
      @property
      def content(self):
 -        if not self._content:
 +        if self._content is None:
              self._content = self.inline or self._fetch_content()
          return self._content
  
@@@ -1424,26 -1409,22 +1424,26 @@@ class StylesheetAsset(WebAsset)
              content = super(StylesheetAsset, self)._fetch_content()
              web_dir = os.path.dirname(self.url)
  
 -            content = self.rx_import.sub(
 -                r"""@import \1%s/""" % (web_dir,),
 -                content,
 -            )
 +            if self.rx_import:
 +                content = self.rx_import.sub(
 +                    r"""@import \1%s/""" % (web_dir,),
 +                    content,
 +                )
  
 -            content = self.rx_url.sub(
 -                r"url(\1%s/" % (web_dir,),
 -                content,
 -            )
 +            if self.rx_url:
 +                content = self.rx_url.sub(
 +                    r"url(\1%s/" % (web_dir,),
 +                    content,
 +                )
  
 -            # remove charset declarations, we only support utf-8
 -            content = self.rx_charset.sub('', content)
 +            if self.rx_charset:
 +                # remove charset declarations, we only support utf-8
 +                content = self.rx_charset.sub('', content)
 +
 +            return content
          except AssetError, e:
              self.bundle.css_errors.append(e.message)
              return ''
 -        return content
  
      def minify(self):
          # remove existing sourcemaps, make no sense after re-mini
          else:
              return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
  
 -class SassAsset(StylesheetAsset):
 +class PreprocessedCSS(StylesheetAsset):
      html_url = '%s.css'
 -    rx_indent = re.compile(r'^( +|\t+)', re.M)
 -    indent = None
 -    reindent = '    '
 +    rx_import = None
  
      def minify(self):
          return self.with_header()
          if self.url:
              ira = self.registry['ir.attachment']
              url = self.html_url % self.url
 -            domain = [('type', '=', 'binary'), ('url', '=', self.url)]
 +            domain = [('type', '=', 'binary'), ('url', '=', url)]
              ira_id = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
 +            datas = self.content.encode('utf8').encode('base64')
              if ira_id:
                  # TODO: update only if needed
 -                ira.write(self.cr, openerp.SUPERUSER_ID, [ira_id], {'datas': self.content}, context=self.context)
 +                ira.write(self.cr, openerp.SUPERUSER_ID, ira_id, {'datas': datas}, context=self.context)
              else:
                  ira.create(self.cr, openerp.SUPERUSER_ID, dict(
 -                    datas=self.content.encode('utf8').encode('base64'),
 +                    datas=datas,
                      mimetype='text/css',
                      type='binary',
                      name=url,
                      url=url,
                  ), context=self.context)
 -        return super(SassAsset, self).to_html()
 +        return super(PreprocessedCSS, self).to_html()
 +
 +    def get_source(self):
 +        content = self.inline or self._fetch_content()
 +        return "/*! %s */\n%s" % (self.id, content)
 +
 +    def get_command(self):
 +        raise NotImplementedError
 +
 +class SassStylesheetAsset(PreprocessedCSS):
 +    rx_indent = re.compile(r'^( +|\t+)', re.M)
 +    indent = None
 +    reindent = '    '
  
      def get_source(self):
          content = textwrap.dedent(self.inline or self._fetch_content())
  
          def fix_indent(m):
 +            # Indentation normalization
              ind = m.group()
              if self.indent is None:
                  self.indent = ind
              pass
          return "/*! %s */\n%s" % (self.id, content)
  
 +    def get_command(self):
 +        defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
 +        sass = which('sass', path=os.pathsep.join(defpath))
 +        return [sass, '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
 +                '-r', 'bootstrap-sass']
 +
 +class LessStylesheetAsset(PreprocessedCSS):
 +    def get_command(self):
 +        defpath = os.environ.get('PATH', os.defpath).split(os.pathsep)
 +        if os.name == 'nt':
 +            lessc = which('lessc.cmd', path=os.pathsep.join(defpath))
 +        else:
 +            lessc = which('lessc', path=os.pathsep.join(defpath))
 +        webpath = openerp.http.addons_manifest['web']['addons_path']
 +        lesspath = os.path.join(webpath, 'web', 'static', 'lib', 'bootstrap', 'less')
 +        return [lessc, '-', '--clean-css', '--no-js', '--no-color', '--include-path=%s' % lesspath]
 +
  def rjsmin(script):
      """ Minify js with a clever regex.
      Taken from http://opensource.perlig.de/rjsmin
@@@ -37,10 -37,8 +37,10 @@@ from lxml import etre
  import openerp
  from openerp import tools, api
  from openerp.http import request
 +from openerp.modules.module import get_resource_path, get_resource_from_path
  from openerp.osv import fields, osv, orm
 -from openerp.tools import graph, SKIPPED_ELEMENT_TYPES, SKIPPED_ELEMENTS
 +from openerp.tools import config, graph, SKIPPED_ELEMENT_TYPES, SKIPPED_ELEMENTS
 +from openerp.tools.convert import _fix_multiple_roots
  from openerp.tools.parse_version import parse_version
  from openerp.tools.safe_eval import safe_eval as eval
  from openerp.tools.view_validation import valid_view
@@@ -107,31 -105,6 +107,31 @@@ def _hasclass(context, *cls)
  
      return node_classes.issuperset(cls)
  
 +def get_view_arch_from_file(filename, xmlid):
 +    doc = etree.parse(filename)
 +    node = None
 +    for n in doc.xpath('//*[@id="%s"] | //*[@id="%s"]' % (xmlid, xmlid.split('.')[1])):
 +        if n.tag in ('template', 'record'):
 +            node = n
 +            break
 +    if node is not None:
 +        if node.tag == 'record':
 +            field = node.find('field[@name="arch"]')
 +            _fix_multiple_roots(field)
 +            inner = ''.join([etree.tostring(child) for child in field.iterchildren()])
 +            return field.text + inner
 +        elif node.tag == 'template':
 +            # The following dom operations has been copied from convert.py's _tag_template()
 +            if not node.get('inherit_id'):
 +                node.set('t-name', xmlid)
 +                node.tag = 't'
 +            else:
 +                node.tag = 'data'
 +            node.attrib.pop('id', None)
 +            return etree.tostring(node)
 +    _logger.warning("Could not find view arch definition in file '%s' for xmlid '%s'" % (filename, xmlid))
 +    return None
 +
  xpath_utils = etree.FunctionNamespace(None)
  xpath_utils['hasclass'] = _hasclass
  
@@@ -150,53 -123,22 +150,53 @@@ class view(osv.osv)
          data_ids = IMD.search_read(cr, uid, [('id', 'in', ids), ('model', '=', 'ir.ui.view')], ['res_id'], context=context)
          return map(itemgetter('res_id'), data_ids)
  
 +    def _arch_get(self, cr, uid, ids, name, arg, context=None):
 +        result = {}
 +        for view in self.browse(cr, uid, ids, context=context):
 +            arch_fs = None
 +            if config['dev_mode'] and view.arch_fs and view.xml_id:
 +                # It is safe to split on / herebelow because arch_fs is explicitely stored with '/'
 +                fullpath = get_resource_path(*view.arch_fs.split('/'))
 +                arch_fs = get_view_arch_from_file(fullpath, view.xml_id)
 +            result[view.id] = arch_fs or view.arch_db
 +        return result
 +
 +    def _arch_set(self, cr, uid, ids, field_name, field_value, args, context=None):
 +        if not isinstance(ids, list):
 +            ids = [ids]
 +        if field_value:
 +            for view in self.browse(cr, uid, ids, context=context):
 +                data = dict(arch_db=field_value)
 +                key = 'install_mode_data'
 +                if context and key in context:
 +                    imd = context[key]
 +                    if self._model._name == imd['model'] and (not view.xml_id or view.xml_id == imd['xml_id']):
 +                        # we store the relative path to the resource instead of the absolute path
 +                        data['arch_fs'] = '/'.join(get_resource_from_path(imd['xml_file'])[0:2])
 +                self.write(cr, uid, ids, data, context=context)
 +
 +        return True
 +
      _columns = {
          'name': fields.char('View Name', required=True),
          'model': fields.char('Object', select=True),
 +        'key': fields.char(string='Key'),
          'priority': fields.integer('Sequence', required=True),
          'type': fields.selection([
              ('tree','Tree'),
              ('form','Form'),
              ('graph', 'Graph'),
 +            ('pivot', 'Pivot'),
              ('calendar', 'Calendar'),
              ('diagram','Diagram'),
              ('gantt', 'Gantt'),
              ('kanban', 'Kanban'),
              ('search','Search'),
              ('qweb', 'QWeb')], string='View Type'),
 -        'arch': fields.text('View Architecture', required=True),
 +        'arch': fields.function(_arch_get, fnct_inv=_arch_set, string='View Architecture', type="text", nodrop=True),
 +        'arch_db': fields.text('Arch Blob'),
 +        'arch_fs': fields.char('Arch Filename'),
-         'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True),
+         'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='restrict', select=True),
          'inherit_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'),
          'field_parent': fields.char('Child Field'),
          'model_data_id': fields.function(_get_model_data, type='many2one', relation='ir.model.data', string="Model Data",
          if context is None:
              context = {}
  
 +        # If view is modified we remove the arch_fs information thus activating the arch_db
 +        # version. An `init` of the view will restore the arch_fs for the --dev mode
 +        if 'arch' in vals and 'install_mode_data' not in context:
 +            vals['arch_fs'] = False
 +
          # drop the corresponding view customizations (used for dashboards for example), otherwise
          # not all users would see the updated views
          custom_view_ids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('ref_id', 'in', ids)])
      #------------------------------------------------------
      # QWeb template views
      #------------------------------------------------------
 -    @tools.ormcache_context(accepted_keys=('lang','inherit_branding', 'editable', 'translatable'))
 -    def read_template(self, cr, uid, xml_id, context=None):
 -        if isinstance(xml_id, (int, long)):
 -            view_id = xml_id
 -        else:
 -            if '.' not in xml_id:
 -                raise ValueError('Invalid template id: %r' % (xml_id,))
 -            view_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, xml_id, raise_if_not_found=True)
 -
 +    _read_template_cache = dict(accepted_keys=('lang','inherit_branding', 'editable', 'translatable'))
 +    if config['dev_mode']:
 +        _read_template_cache['size'] = 0
 +    @tools.ormcache_context(**_read_template_cache)
 +    def _read_template(self, cr, uid, view_id, context=None):
          arch = self.read_combined(cr, uid, view_id, fields=['arch'], context=context)['arch']
          arch_tree = etree.fromstring(arch)
  
          arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
          return arch
  
 +    def read_template(self, cr, uid, xml_id, context=None):
 +        if isinstance(xml_id, (int, long)):
 +            view_id = xml_id
 +        else:
 +            if '.' not in xml_id:
 +                raise ValueError('Invalid template id: %r' % (xml_id,))
 +            view_id = self.get_view_id(cr, uid, xml_id, context=context)
 +        return self._read_template(cr, uid, view_id, context=context)
 +
 +    @tools.ormcache(skiparg=3)
 +    def get_view_id(self, cr, uid, xml_id, context=None):
 +        return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, xml_id, raise_if_not_found=True)
 +
      def clear_cache(self):
 -        self.read_template.clear_cache(self)
 +        self._read_template.clear_cache(self)
  
      def _contains_branded(self, node):
          return node.tag == 't'\
@@@ -235,7 -235,7 +235,7 @@@ class res_partner(osv.Model, format_add
          'parent_id': fields.many2one('res.partner', 'Related Company', select=True),
          'parent_name': fields.related('parent_id', 'name', type='char', readonly=True, string='Parent name'),
          'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts', domain=[('active','=',True)]), # force "active_test" domain to bypass _search() override
 -        'ref': fields.char('Contact Reference', select=1),
 +        'ref': fields.char('Internal Reference', select=1),
          'lang': fields.selection(_lang_get, 'Language',
              help="If the selected language is loaded in the system, all documents related to this contact will be printed in this language. If not, it will be English."),
          'tz': fields.selection(_tz_get,  'Timezone', size=64,
          'credit_limit': fields.float(string='Credit Limit'),
          'ean13': fields.char('EAN13', size=13),
          'active': fields.boolean('Active'),
 -        'customer': fields.boolean('Customer', help="Check this box if this contact is a customer."),
 -        'supplier': fields.boolean('Supplier', help="Check this box if this contact is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
 +        'customer': fields.boolean('Is a Customer', help="Check this box if this contact is a customer."),
 +        'supplier': fields.boolean('Is a Supplier', help="Check this box if this contact is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
          'employee': fields.boolean('Employee', help="Check this box if this contact is an Employee."),
          'function': fields.char('Job Position'),
          'type': fields.selection([('default', 'Default'), ('invoice', 'Invoice'),
              help="Small-sized image of this contact. It is automatically "\
                   "resized as a 64x64px image, with aspect ratio preserved. "\
                   "Use this field anywhere a small image is required."),
 -        'has_image': fields.function(_has_image, type="boolean"),
 +        'has_image': fields.function(_has_image, string="Has image", type="boolean"),
          'company_id': fields.many2one('res.company', 'Company', select=1),
          'color': fields.integer('Color Index'),
          'user_ids': fields.one2many('res.users', 'partner_id', 'Users'),
              if not parent.is_company:
                  parent.write({'is_company': True})
  
+     def unlink(self, cr, uid, ids, context=None):
+         orphan_contact_ids = self.search(cr, uid,
+             [('parent_id', 'in', ids), ('id', 'not in', ids), ('use_parent_address', '=', True)], context=context)
+         if orphan_contact_ids:
+             # no longer have a parent address
+             self.write(cr, uid, orphan_contact_ids, {'use_parent_address': False}, context=context)
+         return super(res_partner, self).unlink(cr, uid, ids, context=context)
      def _clean_website(self, website):
          (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(website)
          if not scheme:
@@@ -10,7 -10,7 +10,7 @@@
  
              <menuitem id="base.menu_sales" parent="base.menu_base_partner" name="Sales" sequence="1" />
              <menuitem id="menu_base_config" parent="menu_base_partner" name="Configuration" sequence="30" groups="group_system"/>
 -            <menuitem id="menu_config_address_book" parent="menu_base_config" name="Address Book" sequence="40" groups="group_system"/>
 +            <menuitem id="menu_config_address_book" parent="menu_base_config" name="Contacts" sequence="4" groups="group_system"/>
  
          <!-- Partner Titles -->
          <record id="view_partner_title_tree" model="ir.ui.view">
@@@ -47,7 -47,7 +47,7 @@@
          -->
  
          <record id="action_partner_title_partner" model="ir.actions.act_window">
 -            <field name="name">Titles</field>
 +            <field name="name">Company Titles</field>
              <field name="type">ir.actions.act_window</field>
              <field name="res_model">res.partner.title</field>
              <field name="view_type">form</field>
  
                      <group>
                          <group>
 -                            <label for="type" attrs="{'invisible': [('parent_id','=', False)]}"/>
 -                            <div attrs="{'invisible': [('parent_id','=', False)]}" name="div_type">
 +                            <label for="type" attrs="{'invisible': [('parent_id','=', False)]}" groups="base.group_no_one"/>
 +                            <div attrs="{'invisible': [('parent_id','=', False)]}" name="div_type" groups="base.group_no_one">
                                  <field class="oe_inline"
                                      name="type"/>
                              </div>
                                              <div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '')" style="position: relative">
                                                  <a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
                                                  <div class="oe_module_vignette">
 -                                                <a type="open">
 +                                                <a type="open" class="col-md-2">
                                                      <t t-if="record.has_image.raw_value === true">
                                                          <img t-att-src="kanban_image('res.partner', 'image', record.id.value, {'preview_image': 'image_small'})" class="oe_avatar oe_kanban_avatar_smallbox"/>
                                                      </t>
                                                  </a>
                                                      <div class="oe_module_desc">
                                                          <div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_color_border">
 -                                                            <table class="oe_kanban_table">
 -                                                                <tr>
 -                                                                    <td class="oe_kanban_title1" align="left" valign="middle">
 -                                                                        <h4><a type="open"><field name="name"/></a></h4>
 +                                                              <div class="col-md-12">
 +                                                                 <div>
 +                                                                     <h4><a type="open"><field name="name"/></a></h4>
                                                                          <i><div t-if="record.function.raw_value">
                                                                              <field name="function"/></div></i>
                                                                          <div><a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value">
                                                                          <div t-if="record.phone.raw_value">Phone: <field name="phone"/></div>
                                                                          <div t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></div>
                                                                          <div t-if="record.fax.raw_value">Fax: <field name="fax"/></div>
 -                                                                    </td>
 -                                                                </tr>
 -                                                            </table>
 +                                                                  </div>
 +                                                               </div>
                                                          </div>
                                                      </div>
                                                  </div>
                                              <field name="email"/>
                                              <field name="phone"/>
                                              <field name="mobile"/>
 +                                            <field name="title"/>
                                          </group>
                                          <div>
                                              <field name="use_parent_address"/><label for="use_parent_address"/>
                                          </div>
                                          <group>
 -                                            <label for="type"/>
 -                                            <div name="div_type">
 +                                            <label for="type" groups="base.group_no_one"/>
 +                                            <div name="contact_div_type" groups="base.group_no_one">
                                                  <field class="oe_inline" name="type"/>
                                              </div>
                                              <label for="street" string="Address" attrs="{'invisible': [('use_parent_address','=', True)]}"/>
                              <field name="comment" placeholder="Put an internal note..."/>
                          </page>
                          <page name='sales_purchases' string="Sales &amp; Purchases">
 -                            <group>
 -                                <group>
 -                                    <field name="user_id" 
 -                                        context="{'default_groups_ref': ['base.group_partner_manager']}"/>
 -                                    <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
 -                                </group>
 -                                <group>
 -                                    <field name="customer"/>
 -                                    <field name="supplier"/>
 -                                </group>
 +                            <group name="container_row_1">
                                  <group>
                                      <field name="ref"/>
                                      <field name="lang"/>
 -                                    <field name="date"/>
                                  </group>
                                  <group>
-                                     <field name="company_id" groups="base.group_multi_company" widget="selection"/>
 -                                    <field name="active"/>
++                                    <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
 +                                    <field name="active"/>                                    
                                  </group>
                              </group>
 +                            <group name="container_row_2">
 +                                <group string="Sale" name="sale">
 +                                    <field name="customer"/>
 +                                    <field name="user_id" 
 +                                        context="{'default_groups_ref': ['base.group_partner_manager']}"/>
 +                                </group>
 +                                <group string="Purchase" name="purchase">
 +                                    <field name="supplier"/>
 +                                </group>
 +                            </group>
 +                            <group name="container_row_3">
 +                                <group name="container_left"/>
 +                                <group name="container_right"/>
 +                            </group>
                          </page>
                      </notebook>
                  </sheet>
              <field name="res_model">res.partner</field>
              <field name="view_type">form</field>
              <field name="view_mode">kanban,tree,form</field>
-             <field name="domain">[('customer','=',True)]</field>
+             <field name="domain">[]</field>
              <field name="context">{'default_customer':1, 'search_default_customer':1}</field>
              <field name="filter" eval="True"/>
              <field name="help" type="html">
              </field>
          </record>
  
 -        <menuitem action="action_partner_category_form" id="menu_partner_category_form" name="Partner Tags" sequence="4" parent="menu_config_address_book" groups="base.group_no_one"/>
 +        <menuitem action="action_partner_category_form" id="menu_partner_category_form" name="Tags" sequence="1" parent="menu_config_address_book" groups="base.group_no_one"/>
  
      </data>
  </openerp>
@@@ -91,7 -91,7 +91,7 @@@ class res_groups(osv.osv)
      _columns = {
          'name': fields.char('Name', required=True, translate=True),
          'users': fields.many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', 'Users'),
-         'model_access': fields.one2many('ir.model.access', 'group_id', 'Access Controls'),
+         'model_access': fields.one2many('ir.model.access', 'group_id', 'Access Controls', copy=True),
          'rule_groups': fields.many2many('ir.rule', 'rule_group_rel',
              'group_id', 'rule_group_id', 'Rules', domain=[('global', '=', False)]),
          'menu_access': fields.many2many('ir.ui.menu', 'ir_ui_menu_group_rel', 'gid', 'menu_id', 'Access Menu'),
@@@ -163,7 -163,7 +163,7 @@@ class res_users(osv.osv)
  
      _columns = {
          'id': fields.integer('ID'),
 -        'login_date': fields.date('Latest connection', select=1, copy=False),
 +        'login_date': fields.datetime('Latest connection', select=1, copy=False),
          'partner_id': fields.many2one('res.partner', required=True,
              string='Related Partner', ondelete='restrict',
              help='Partner-related data of the user', auto_join=True),
diff --combined openerp/models.py
@@@ -2269,7 -2269,7 +2269,7 @@@ class BaseModel(object)
                  _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']]:
          elif 'x_name' in cls._fields:
              cls._rec_name = 'x_name'
  
-     def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
-         """ fields_get([fields])
+     def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None):
+         """ fields_get([fields][, attributes])
  
          Return the definition of each field.
  
          dictionaries. The _inherits'd fields are included. The string, help,
          and selection (if present) attributes are translated.
  
-         :param cr: database cursor
-         :param user: current user id
-         :param allfields: list of fields
-         :param context: context arguments, like lang, time zone
-         :return: dictionary of field dictionaries, each one describing a field of the business object
-         :raise AccessError: * if user has no create/write rights on the requested object
+         :param allfields: list of fields to document, all if empty or not provided
+         :param attributes: list of description attributes to return for each field, all if empty or not provided
          """
          recs = self.browse(cr, user, [], context)
  
+         has_access = functools.partial(recs.check_access_rights, raise_exception=False)
+         readonly = not (has_access('write') or has_access('create'))
          res = {}
          for fname, field in self._fields.iteritems():
              if allfields and fname not in allfields:
                  continue
              if field.groups and not recs.user_has_groups(field.groups):
                  continue
-             res[fname] = field.get_description(recs.env)
  
-         # if user cannot create or modify records, make all fields readonly
-         has_access = functools.partial(recs.check_access_rights, raise_exception=False)
-         if not (has_access('write') or has_access('create')):
-             for description in res.itervalues():
+             description = field.get_description(recs.env)
+             if readonly:
                  description['readonly'] = True
                  description['states'] = {}
+             if attributes:
+                 description = {k: v for k, v in description.iteritems()
+                                if k in attributes}
+             res[fname] = description
  
          return res
  
          :raise ValidateError: if user tries to enter invalid value for a field that is not in selection
          :raise UserError: if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)
  
-         .. _openerp/models/relationals/format:
-         .. note:: Relational fields use a special "commands" format to manipulate their values
-             This format is a list of command triplets executed sequentially,
-             possible command triplets are:
-             ``(0, _, values: dict)``
-                 links to a new record created from the provided values
-             ``(1, id, values: dict)``
-                 updates the already-linked record of id ``id`` with the
-                 provided ``values``
-             ``(2, id, _)``
-                 unlinks and deletes the linked record of id ``id``
-             ``(3, id, _)``
-                 unlinks the linked record of id ``id`` without deleting it
-             ``(4, id, _)``
-                 links to an existing record of id ``id``
-             ``(5, _, _)``
-                 unlinks all records in the relation, equivalent to using
-                 the command ``3`` on every linked record
-             ``(6, _, ids)``
-                 replaces the existing list of linked records by the provoded
-                 ones, equivalent to using ``5`` then ``4`` for each id in
-                 ``ids``)
-             (in command triplets, ``_`` values are ignored and can be
-             anything, generally ``0`` or ``False``)
-             Any command can be used on :class:`~openerp.fields.Many2many`,
-             only ``0``, ``1`` and ``2`` can be used on
-             :class:`~openerp.fields.One2many`.
+         * For numeric fields (:class:`~openerp.fields.Integer`,
+           :class:`~openerp.fields.Float`) the value should be of the
+           corresponding type
+         * For :class:`~openerp.fields.Boolean`, the value should be a
+           :class:`python:bool`
+         * For :class:`~openerp.fields.Selection`, the value should match the
+           selection values (generally :class:`python:str`, sometimes
+           :class:`python:int`)
+         * For :class:`~openerp.fields.Many2one`, the value should be the
+           database identifier of the record to set
+         * Other non-relational fields use a string for value
+           .. danger::
+               for historical and compatibility reasons,
+               :class:`~openerp.fields.Date` and
+               :class:`~openerp.fields.Datetime` fields use strings as values
+               (written and read) rather than :class:`~python:datetime.date` or
+               :class:`~python:datetime.datetime`. These date strings are
+               UTC-only and formatted according to
+               :const:`openerp.tools.misc.DEFAULT_SERVER_DATE_FORMAT` and
+               :const:`openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT`
+         * .. _openerp/models/relationals/format:
+           :class:`~openerp.fields.One2many` and
+           :class:`~openerp.fields.Many2many` use a special "commands" format to
+           manipulate the set of records stored in/associated with the field.
+           This format is a list of triplets executed sequentially, where each
+           triplet is a command to execute on the set of records. Not all
+           commands apply in all situations. Possible commands are:
+           ``(0, _, values)``
+               adds a new record created from the provided ``value`` dict.
+           ``(1, id, values)``
+               updates an existing record of id ``id`` with the values in
+               ``values``. Can not be used in :meth:`~.create`.
+           ``(2, id, _)``
+               removes the record of id ``id`` from the set, then deletes it
+               (from the database). Can not be used in :meth:`~.create`.
+           ``(3, id, _)``
+               removes the record of id ``id`` from the set, but does not
+               delete it. Can not be used on
+               :class:`~openerp.fields.One2many`. Can not be used in
+               :meth:`~.create`.
+           ``(4, id, _)``
+               adds an existing record of id ``id`` to the set. Can not be
+               used on :class:`~openerp.fields.One2many`.
+           ``(5, _, _)``
+               removes all records from the set, equivalent to using the
+               command ``3`` on every record explicitly. Can not be used on
+               :class:`~openerp.fields.One2many`. Can not be used in
+               :meth:`~.create`.
+           ``(6, _, ids)``
+               replaces all existing records in the set by the ``ids`` list,
+               equivalent to using the command ``5`` followed by a command
+               ``4`` for each ``id`` in ``ids``. Can not be used on
+               :class:`~openerp.fields.One2many`.
+           .. note:: Values marked as ``_`` in the list above are ignored and
+                     can be anything, generally ``0`` or ``False``.
          """
          if not self:
              return True
          return record
  
      #
-     # Dirty flag, to mark records modified (in draft mode)
+     # Dirty flags, to mark record fields modified (in draft mode)
      #
  
-     @property
-     def _dirty(self):
+     def _is_dirty(self):
          """ Return whether any record in `self` is dirty. """
          dirty = self.env.dirty
          return any(record in dirty for record in self)
  
-     @_dirty.setter
-     def _dirty(self, value):
-         """ Mark the records in `self` as dirty. """
-         if value:
-             map(self.env.dirty.add, self)
-         else:
-             map(self.env.dirty.discard, self)
+     def _get_dirty(self):
+         """ Return the list of field names for which `self` is dirty. """
+         dirty = self.env.dirty
+         return list(dirty.get(self, ()))
+     def _set_dirty(self, field_name):
+         """ Mark the records in `self` as dirty for the given `field_name`. """
+         dirty = self.env.dirty
+         for record in self:
+             dirty[record].add(field_name)
  
      #
      # "Dunder" methods
                      field = self._fields[name]
                      newval = record[name]
                      if field.type in ('one2many', 'many2many'):
-                         if newval != oldval or newval._dirty:
+                         if newval != oldval or newval._is_dirty():
                              # put new value in result
                              result['value'][name] = field.convert_to_write(
                                  newval, record._origin, subfields.get(name),
@@@ -9,10 -9,6 +9,6 @@@ import os.pat
  import platform
  import psutil
  import random
- if os.name == 'posix':
-     import resource
- else:
-     resource = None
  import select
  import signal
  import socket
@@@ -24,10 -20,15 +20,15 @@@ import unittest
  
  import werkzeug.serving
  
- try:
+ if os.name == 'posix':
+     # Unix only for workers
      import fcntl
- except ImportError:
-     pass
+     import resource
+ else:
+     # Windows shim
+     signal.SIGHUP = -1
+ # Optional process names for workers
  try:
      from setproctitle import setproctitle
  except ImportError:
@@@ -41,13 -42,6 +42,13 @@@ from openerp.tools.misc import stripped
  
  _logger = logging.getLogger(__name__)
  
 +try:
 +    import watchdog
 +    from watchdog.observers import Observer
 +    from watchdog.events import FileCreatedEvent, FileModifiedEvent
 +except ImportError:
 +    watchdog = None
 +
  SLEEP_INTERVAL = 60     # 1 min
  
  #----------------------------------------------------------
@@@ -112,37 -106,90 +113,37 @@@ class ThreadedWSGIServerReloadable(Logg
              super(ThreadedWSGIServerReloadable, self).server_activate()
  
  #----------------------------------------------------------
 -# AutoReload watcher
 +# FileSystem Watcher for autoreload and cache invalidation
  #----------------------------------------------------------
 -
 -class AutoReload(object):
 -    def __init__(self, server):
 -        self.server = server
 -        self.files = {}
 -        self.modules = {}
 -        import pyinotify
 -        class EventHandler(pyinotify.ProcessEvent):
 -            def __init__(self, autoreload):
 -                self.autoreload = autoreload
 -
 -            def process_IN_CREATE(self, event):
 -                _logger.debug('File created: %s', event.pathname)
 -                self.autoreload.files[event.pathname] = 1
 -
 -            def process_IN_MODIFY(self, event):
 -                _logger.debug('File modified: %s', event.pathname)
 -                self.autoreload.files[event.pathname] = 1
 -
 -        self.wm = pyinotify.WatchManager()
 -        self.handler = EventHandler(self)
 -        self.notifier = pyinotify.Notifier(self.wm, self.handler, timeout=0)
 -        mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE  # IN_MOVED_FROM, IN_MOVED_TO ?
 +class FSWatcher(object):
 +    def __init__(self):
 +        self.observer = Observer()
          for path in openerp.modules.module.ad_paths:
              _logger.info('Watching addons folder %s', path)
 -            self.wm.add_watch(path, mask, rec=True)
 -
 -    def process_data(self, files):
 -        xml_files = [i for i in files if i.endswith('.xml')]
 -        for i in xml_files:
 -            for path in openerp.modules.module.ad_paths:
 -                if i.startswith(path):
 -                    # find out wich addons path the file belongs to
 -                    # and extract it's module name
 -                    right = i[len(path) + 1:].split('/')
 -                    if len(right) < 2:
 -                        continue
 -                    module = right[0]
 -                    self.modules[module] = 1
 -        if self.modules:
 -            _logger.info('autoreload: xml change detected, autoreload activated')
 -            restart()
 -
 -    def process_python(self, files):
 -        # process python changes
 -        py_files = [i for i in files if i.endswith('.py')]
 -        py_errors = []
 -        # TODO keep python errors until they are ok
 -        if py_files:
 -            for i in py_files:
 -                try:
 -                    source = open(i, 'rb').read() + '\n'
 -                    compile(source, i, 'exec')
 -                except SyntaxError:
 -                    py_errors.append(i)
 -            if py_errors:
 -                _logger.info('autoreload: python code change detected, errors found')
 -                for i in py_errors:
 -                    _logger.info('autoreload: SyntaxError %s', i)
 -            else:
 -                _logger.info('autoreload: python code updated, autoreload activated')
 -                restart()
 -
 -    def check_thread(self):
 -        # Check if some files have been touched in the addons path.
 -        # If true, check if the touched file belongs to an installed module
 -        # in any of the database used in the registry manager.
 -        while 1:
 -            while self.notifier.check_events(1000):
 -                self.notifier.read_events()
 -                self.notifier.process_events()
 -            l = self.files.keys()
 -            self.files.clear()
 -            self.process_data(l)
 -            self.process_python(l)
 +            self.observer.schedule(self, path, recursive=True)
 +
 +    def dispatch(self, event):
 +        if isinstance(event, (FileCreatedEvent, FileModifiedEvent)):
 +            if not event.is_directory:
 +                path = event.src_path
 +                if path.endswith('.py'):
 +                    try:
 +                        source = open(path, 'rb').read() + '\n'
 +                        compile(source, path, 'exec')
 +                    except SyntaxError:
 +                        _logger.error('autoreload: python code change detected, SyntaxError in %s', path)
 +                    else:
 +                        _logger.info('autoreload: python code updated, autoreload activated')
 +                        restart()
  
 -    def run(self):
 -        t = threading.Thread(target=self.check_thread)
 -        t.setDaemon(True)
 -        t.start()
 +    def start(self):
 +        self.observer.start()
          _logger.info('AutoReload watcher running')
  
 +    def stop(self):
 +        self.observer.stop()
 +        self.observer.join()
 +
  #----------------------------------------------------------
  # Servers: Threaded, Gevented and Prefork
  #----------------------------------------------------------
@@@ -628,8 -675,6 +629,6 @@@ class Worker(object)
                  raise
  
      def process_limit(self):
-         if resource is None:
-             return
          # If our parent changed sucide
          if self.ppid != os.getppid():
              _logger.info("Worker (%s) Parent changed", self.pid)
@@@ -814,8 -859,7 +813,8 @@@ def _reexec(updated_modules=None)
          subprocess.call('net stop {0} && net start {0}'.format(nt_service_name), shell=True)
      exe = os.path.basename(sys.executable)
      args = stripped_sys_argv()
 -    args += ["-u", ','.join(updated_modules)]
 +    if updated_modules:
 +        args += ["-u", ','.join(updated_modules)]
      if not args or args[0] != exe:
          args.insert(0, exe)
      os.execv(sys.executable, args)
@@@ -887,21 -931,18 +886,21 @@@ def start(preload=None, stop=False)
      else:
          server = ThreadedServer(openerp.service.wsgi_server.application)
  
 -    if config['auto_reload']:
 -        autoreload = AutoReload(server)
 -        autoreload.run()
 +    watcher = None
 +    if config['dev_mode']:
 +        if watchdog:
 +            watcher = FSWatcher()
 +            watcher.start()
 +        else:
 +            _logger.warning("'watchdog' module not installed. Code autoreload feature is disabled")
  
      rc = server.run(preload, stop)
  
      # like the legend of the phoenix, all ends with beginnings
      if getattr(openerp, 'phoenix', False):
 -        modules = []
 -        if config['auto_reload']:
 -            modules = autoreload.modules.keys()
 -        _reexec(modules)
 +        if watcher:
 +            watcher.stop()
 +        _reexec()
  
      return rc if rc else 0
  
diff --combined openerp/tools/convert.py
@@@ -692,17 -692,9 +692,17 @@@ form: module.record_id""" % (xml_id,
          rec_model = rec.get("model").encode('ascii')
          model = self.pool[rec_model]
          rec_id = rec.get("id",'').encode('ascii')
 -        rec_context = rec.get("context", None)
 +        rec_context = rec.get("context", {})
          if rec_context:
              rec_context = unsafe_eval(rec_context)
 +
 +        if self.xml_filename and rec_id:
 +            rec_context['install_mode_data'] = dict(
 +                xml_file=self.xml_filename,
 +                xml_id=rec_id,
 +                model=rec_model,
 +            )
 +
          self._test_xml_id(rec_id)
          # in update mode, the record won't be updated if the data node explicitely
          # opt-out using @noupdate="1". A second check will be performed in
  
          record = etree.Element('record', attrib=record_attrs)
          record.append(Field(name, name='name'))
 +        record.append(Field(full_tpl_id, name='key'))
          record.append(Field("qweb", name='type'))
          record.append(Field(el.get('priority', "16"), name='priority'))
          if 'inherit_id' in el.attrib:
              record.append(Field(name='inherit_id', ref=el.get('inherit_id')))
-         if el.get('active') in ("True", "False") and mode != "update":
-             record.append(Field(name='active', eval=el.get('active')))
+         if el.get('active') in ("True", "False"):
+             view_id = self.id_get(cr, tpl_id, raise_if_not_found=False)
+             if mode != "update" or not view_id:
+                 record.append(Field(name='active', eval=el.get('active')))
          if el.get('customize_show') in ("True", "False"):
              record.append(Field(name='customize_show', eval=el.get('customize_show')))
          groups = el.attrib.pop('groups', None)
  
          return self._tag_record(cr, record, data_node)
  
-     def id_get(self, cr, id_str):
+     def id_get(self, cr, id_str, raise_if_not_found=True):
          if id_str in self.idref:
              return self.idref[id_str]
-         res = self.model_id_get(cr, id_str)
+         res = self.model_id_get(cr, id_str, raise_if_not_found)
          if res and len(res)>1: res = res[1]
          return res
  
-     def model_id_get(self, cr, id_str):
+     def model_id_get(self, cr, id_str, raise_if_not_found=True):
          model_data_obj = self.pool['ir.model.data']
          mod = self.module
-         if '.' in id_str:
-             mod,id_str = id_str.split('.')
-         return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
+         if '.' not in id_str:
+             id_str = '%s.%s' % (mod, id_str)
+         return model_data_obj.xmlid_to_res_model_res_id(
+             cr, self.uid, id_str,
+             raise_if_not_found=raise_if_not_found)
  
      def parse(self, de, mode=None):
          if de.tag != 'openerp':
                          raise ParseError, (misc.ustr(e), etree.tostring(rec).rstrip(), rec.getroottree().docinfo.URL, rec.sourceline), exc_info[2]
          return True
  
 -    def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
 +    def __init__(self, cr, module, idref, mode, report=None, noupdate=False, xml_filename=None):
  
          self.mode = mode
          self.module = module
              report = assertion_report.assertion_report()
          self.assertion_report = report
          self.noupdate = noupdate
 +        self.xml_filename = xml_filename
          self._tags = {
              'record': self._tag_record,
              'delete': self._tag_delete,
@@@ -989,11 -983,7 +993,11 @@@ def convert_xml_import(cr, module, xmlf
  
      if idref is None:
          idref={}
 -    obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
 +    if isinstance(xmlfile, file):
 +        xml_filename = xmlfile.name
 +    else:
 +        xml_filename = xmlfile
 +    obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate, xml_filename=xml_filename)
      obj.parse(doc.getroot(), mode=mode)
      return True
  
diff --combined openerp/tools/misc.py
@@@ -66,8 -66,11 +66,11 @@@ _logger = logging.getLogger(__name__
  SKIPPED_ELEMENT_TYPES = (etree._Comment, etree._ProcessingInstruction, etree.CommentBase, etree.PIBase)
  
  def find_in_path(name):
+     path = os.environ.get('PATH', os.defpath).split(os.pathsep)
+     if config.get('bin_path') and config['bin_path'] != 'None':
+         path.append(config['bin_path'])
      try:
-         return which(name)
+         return which(name, path=os.pathsep.join(path))
      except IOError:
          return None
  
@@@ -1161,7 -1164,7 +1164,7 @@@ class CountingStream(object)
  
  def stripped_sys_argv(*strip_args):
      """Return sys.argv with some arguments stripped, suitable for reexecution or subprocesses"""
 -    strip_args = sorted(set(strip_args) | set(['-s', '--save', '-d', '--database', '-u', '--update', '-i', '--init']))
 +    strip_args = sorted(set(strip_args) | set(['-s', '--save', '-u', '--update', '-i', '--init']))
      assert all(config.parser.has_option(s) for s in strip_args)
      takes_value = dict((s, config.parser.get_option(s).takes_value()) for s in strip_args)
  
diff --combined setup/win32/setup.nsi
@@@ -238,6 -238,10 +238,10 @@@ Section $(TITLE_OpenERP_Server) Section
      File "start.bat"\r
      File "stop.bat"\r
  \r
+     SetOutPath "$INSTDIR\thirdparty"\r
+     File /r "${STATIC_PATH}\wkhtmltopdf\*"\r
+     File /r "${STATIC_PATH}\less\*"\r
\r
  # If there is a previous install of the OpenERP Server, keep the login/password from the config file\r
      WriteIniStr "$INSTDIR\server\openerp-server.conf" "options" "db_host" $TextPostgreSQLHostname\r
      WriteIniStr "$INSTDIR\server\openerp-server.conf" "options" "db_user" $TextPostgreSQLUsername\r
      WriteIniStr "$INSTDIR\server\openerp-server.conf" "options" "db_port" $TextPostgreSQLPort\r
      # Fix the addons path\r
      WriteIniStr "$INSTDIR\server\openerp-server.conf" "options" "addons_path" "$INSTDIR\server\openerp\addons"\r
+     WriteIniStr "$INSTDIR\server\openerp-server.conf" "options" "bin_path" "$INSTDIR\thirdparty"\r
  \r
      # if we're going to install postgresql force it's path,\r
      # otherwise we consider it's always done and/or correctly tune by users\r
      nsExec::Exec '"$INSTDIR\service\win32_service.exe" -auto -install'\r
  \r
      # TODO: don't hardcode the service name\r
 -    nsExec::Exec "net stop odoo-server-8.0"\r
 +    nsExec::Exec "net stop odoo-server-9.0"\r
      sleep 2\r
  \r
 -    nsExec::Exec "net start odoo-server-8.0"\r
 +    nsExec::Exec "net start odoo-server-9.0"\r
      sleep 2\r
  \r
  SectionEnd\r
@@@ -318,13 -323,13 +323,13 @@@ Section "Uninstall
      ReadRegStr $0 HKLM "${UNINSTALL_REGISTRY_KEY_SERVER}" "UninstallString"\r
      ExecWait '"$0" /S'\r
  \r
 -    nsExec::Exec "net stop odoo-server-8.0"\r
 -    nsExec::Exec "sc delete odoo-server-8.0"\r
 +    nsExec::Exec "net stop odoo-server-9.0"\r
 +    nsExec::Exec "sc delete odoo-server-9.0"\r
      sleep 2\r
  \r
      Rmdir /r "$INSTDIR\server"\r
      Rmdir /r "$INSTDIR\service"\r
\r
+     Rmdir /r "$INSTDIR\thirdparty"\r
      DeleteRegKey HKLM "${UNINSTALL_REGISTRY_KEY}"\r
  SectionEnd\r
  \r