[MERGE] forward port of branch 8.0 up to 2b192be
authorChristophe Simonis <chs@odoo.com>
Thu, 16 Oct 2014 14:04:39 +0000 (16:04 +0200)
committerChristophe Simonis <chs@odoo.com>
Thu, 16 Oct 2014 14:04:39 +0000 (16:04 +0200)
57 files changed:
1  2 
README.md
addons/account/account_bank_statement.py
addons/account/account_invoice.py
addons/account/res_config.py
addons/account_analytic_analysis/account_analytic_analysis.py
addons/calendar/calendar.py
addons/crm/crm_lead.py
addons/event/event.py
addons/hr_holidays/hr_holidays.py
addons/mrp/mrp.py
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/widget_base.js
addons/point_of_sale/static/src/xml/pos.xml
addons/product/product.py
addons/product/product_view.xml
addons/project/project.py
addons/purchase/purchase.py
addons/purchase/purchase_view.xml
addons/stock/static/src/js/widgets.js
addons/stock/stock.py
addons/stock_landed_costs/stock_landed_costs_view.xml
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/openerpframework.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/views.js
addons/web/static/src/xml/base.xml
addons/web_kanban/static/src/js/kanban.js
addons/website/models/ir_http.py
addons/website/models/ir_qweb.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/static/src/js/website.js
addons/website/static/src/js/website.snippets.editor.js
addons/website/static/src/xml/website.editor.xml
addons/website/views/snippets.xml
addons/website_event/data/event_demo.xml
addons/website_event/models/event.py
addons/website_event_sale/static/src/js/website.tour.event_sale.js
addons/website_membership/views/website_membership.xml
addons/website_quote/data/website_quotation_data.xml
addons/website_quote/models/order.py
addons/website_quote/views/website_quotation.xml
addons/website_sale/controllers/main.py
addons/website_sale/views/templates.xml
doc/conf.py
openerp/addons/base/ir/ir_attachment.py
openerp/addons/base/ir/ir_model.py
openerp/addons/base/ir/ir_qweb.py
openerp/addons/base/ir/ir_ui_view.py
openerp/addons/base/res/res_users.py
openerp/models.py
openerp/tools/config.py
openerp/tools/mail.py

diff --combined README.md
+++ b/README.md
@@@ -1,11 -1,13 +1,13 @@@
 -[![Build Status](http://runbot.odoo.com/runbot/badge/default/1/8.0.svg)](http://runbot.odoo.com/runbot)
 +[![Build Status](http://runbot.odoo.com/runbot/badge/default/1/master.svg)](http://runbot.odoo.com/runbot)
  
  Odoo
  ----
  
  Odoo is a suite of web based open source business apps.
  
- It's main apps include an <a href="https://www.odoo.com/page/crm">Open Source CRM</a>, <a href="https://www.odoo.com/page/website-builder">Website Builder</a>, <a href="https://www.odoo.com/page/e-commerce">eCommerce</a>, <a href="https://www.odoo.com/page/project-management">Project Management</a>, <a href="https://www.odoo.com/page/accounting">Billing & Accounting</a>, <a href="https://www.odoo.com/page/point-of-sale">Point of Sale</a>, <a href="https://www.odoo.com/page/employees">Human Resources</a>, Marketing, Manufacturing, Purchase Management, ...  Each application is standalone but you get a full featured <a href="https://www.odoo.com">Open Source ERP</a> if you install several apps as they integrate to each others.
+ The main Odoo Apps include an <a href="https://www.odoo.com/page/crm">Open Source CRM</a>, <a href="https://www.odoo.com/page/website-builder">Website Builder</a>, <a href="https://www.odoo.com/page/e-commerce">eCommerce</a>, <a href="https://www.odoo.com/page/project-management">Project Management</a>, <a href="https://www.odoo.com/page/accounting">Billing & Accounting</a>, <a href="https://www.odoo.com/page/point-of-sale">Point of Sale</a>, <a href="https://www.odoo.com/page/employees">Human Resources</a>, Marketing, Manufacturing, Purchase Management, ...  
+ Odoo Apps can be used as stand-alone applications, but they also integrate seamlessly so you get
+ a full-featured <a href="https://www.odoo.com">Open Source ERP</a> when you install several Apps.
  
  
  Getting started with Odoo development
@@@ -26,7 -28,7 +28,7 @@@ Packages, tarballs and installer
  
      Add this apt repository to your /etc/apt/sources.list file
  
 -        deb http://nightly.openerp.com/8.0/nightly/deb/ ./
 +        deb http://nightly.odoo.com/master/nightly/deb/ ./
  
      Then type:
  
  
      If you plan to use Odoo with a local database, please make sure to install PostgreSQL *before* installing the Odoo Debian package.
  
 -* <a href="http://nightly.odoo.com/8.0/nightly/src/">Source tarballs</a>
 +* <a href="http://nightly.odoo.com/master/nightly/src/">Source tarballs</a>
  
 -* <a href="http://nightly.odoo.com/8.0/nightly/exe/">Windows installer</a>
 +* <a href="http://nightly.odoo.com/master/nightly/exe/">Windows installer</a>
  
 -* <a href="http://nightly.odoo.com/8.0/nightly/rpm/">RPM package</a>
 +* <a href="http://nightly.odoo.com/master/nightly/rpm/">RPM package</a>
  
  
  For Odoo employees
@@@ -221,7 -221,7 +221,7 @@@ class account_bank_statement(osv.osv)
              'ref': st_line.ref,
          }
  
-     def _get_counter_part_account(sefl, cr, uid, st_line, context=None):
+     def _get_counter_part_account(self, cr, uid, st_line, context=None):
          """Retrieve the account to use in the counterpart move.
  
             :param browse_record st_line: account.bank.statement.line record to create the move from.
              return st_line.statement_id.journal_id.default_credit_account_id.id
          return st_line.statement_id.journal_id.default_debit_account_id.id
  
-     def _get_counter_part_partner(sefl, cr, uid, st_line, context=None):
+     def _get_counter_part_partner(self, cr, uid, st_line, context=None):
          """Retrieve the partner to use in the counterpart move.
  
             :param browse_record st_line: account.bank.statement.line record to create the move from.
@@@ -513,16 -513,24 +513,24 @@@ class account_bank_statement_line(osv.o
  
          return data
  
-     def get_reconciliation_proposition(self, cr, uid, st_line, excluded_ids=None, context=None):
-         """ Returns move lines that constitute the best guess to reconcile a statement line. """
+     def _domain_reconciliation_proposition(self, cr, uid, st_line, excluded_ids=None, context=None):
          if excluded_ids is None:
              excluded_ids = []
+         domain = [('ref', '=', st_line.name),
+                   ('reconcile_id', '=', False),
+                   ('state', '=', 'valid'),
+                   ('account_id.reconcile', '=', True),
+                   ('id', 'not in', excluded_ids)]
+         return domain
+     def get_reconciliation_proposition(self, cr, uid, st_line, excluded_ids=None, context=None):
+         """ Returns move lines that constitute the best guess to reconcile a statement line. """
          mv_line_pool = self.pool.get('account.move.line')
  
          # Look for structured communication
          if st_line.name:
-             structured_com_match_domain = [('ref', '=', st_line.name),('reconcile_id', '=', False),('state', '=', 'valid'),('account_id.reconcile', '=', True),('id', 'not in', excluded_ids)]
-             match_id = mv_line_pool.search(cr, uid, structured_com_match_domain, offset=0, limit=1, context=context)
+             domain = self._domain_reconciliation_proposition(cr, uid, st_line, excluded_ids=excluded_ids, context=context)
+             match_id = mv_line_pool.search(cr, uid, domain, offset=0, limit=1, context=context)
              if match_id:
                  mv_line_br = mv_line_pool.browse(cr, uid, match_id, context=context)
                  target_currency = st_line.currency_id or st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
          st_line = self.browse(cr, uid, st_line_id, context=context)
          return self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids, str, offset, limit, count, additional_domain, context=context)
  
-     def get_move_lines_for_reconciliation(self, cr, uid, st_line, excluded_ids=None, str=False, offset=0, limit=None, count=False, additional_domain=None, context=None):
-         """ Find the move lines that could be used to reconcile a statement line. If count is true, only returns the count.
-             :param st_line: the browse record of the statement line
-             :param integers list excluded_ids: ids of move lines that should not be fetched
-             :param boolean count: just return the number of records
-             :param tuples list additional_domain: additional domain restrictions
-         """
+     def _domain_move_lines_for_reconciliation(self, cr, uid, st_line, excluded_ids=None, str=False, additional_domain=None, context=None):
          if excluded_ids is None:
              excluded_ids = []
          if additional_domain is None:
              additional_domain = []
-         mv_line_pool = self.pool.get('account.move.line')
          # 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:
              if str != '/':
                  domain.insert(-1, '|', )
                  domain.append(('name', 'ilike', str))
+         return domain
+     def get_move_lines_for_reconciliation(self, cr, uid, st_line, excluded_ids=None, str=False, offset=0, limit=None, count=False, additional_domain=None, context=None):
+         """ Find the move lines that could be used to reconcile a statement line. If count is true, only returns the count.
  
+             :param st_line: the browse record of the statement line
+             :param integers list excluded_ids: ids of move lines that should not be fetched
+             :param boolean count: just return the number of records
+             :param tuples list additional_domain: additional domain restrictions
+         """
+         mv_line_pool = self.pool.get('account.move.line')
+         domain = self._domain_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, str=str, additional_domain=additional_domain, context=context)
+         
          # Get move lines ; in case of a partial reconciliation, only consider one line
          filtered_lines = []
          reconcile_partial_ids = []
          '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)),
      }
  
@@@ -24,6 -24,7 +24,7 @@@ from lxml import etre
  
  from openerp import models, fields, api, _
  from openerp.exceptions import except_orm, Warning, RedirectWarning
+ from openerp.tools import float_compare
  import openerp.addons.decimal_precision as dp
  
  # mapping invoice type to journal type
@@@ -705,15 -706,16 +706,16 @@@ class account_invoice(models.Model)
                  account_invoice_tax.create(tax)
          else:
              tax_key = []
+             precision = self.env['decimal.precision'].precision_get('Account')
              for tax in self.tax_line:
                  if tax.manual:
                      continue
 -                key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id, tax.account_analytic_id.id)
 +                key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
                  tax_key.append(key)
                  if key not in compute_taxes:
                      raise except_orm(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
                  base = compute_taxes[key]['base']
-                 if abs(base - tax.base) > company_currency.rounding:
+                 if float_compare(abs(base - tax.base), company_currency.rounding, precision_digits=precision) == 1:
                      raise except_orm(_('Warning!'), _('Tax base different!\nClick on compute to update the tax base.'))
              for key in compute_taxes:
                  if key not in tax_key:
                      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'."))
@@@ -1548,15 -1550,7 +1550,15 @@@ class account_invoice_tax(models.Model)
                      val['account_id'] = tax['account_paid_id'] or line.account_id.id
                      val['account_analytic_id'] = tax['account_analytic_paid_id']
  
 -                key = (val['tax_code_id'], val['base_code_id'], val['account_id'], val['account_analytic_id'])
 +                # If the taxes generate moves on the same financial account as the invoice line
 +                # and no default analytic account is defined at the tax level, propagate the
 +                # analytic account from the invoice line to the tax line. This is necessary
 +                # in situations were (part of) the taxes cannot be reclaimed,
 +                # to ensure the tax move is allocated to the proper analytic account.
 +                if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
 +                    val['account_analytic_id'] = line.account_analytic_id.id
 +
 +                key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
                  if not key in tax_grouped:
                      tax_grouped[key] = val
                  else:
@@@ -51,8 -51,8 +51,8 @@@ class account_config_settings(osv.osv_m
          'code_digits': fields.integer('# of Digits', help="No. of digits to use for account code"),
          'tax_calculation_rounding_method': fields.related('company_id',
              'tax_calculation_rounding_method', type='selection', selection=[
 -            ('round_per_line', 'Round per line'),
 -            ('round_globally', 'Round globally'),
 +            ('round_per_line', 'Round calculation of taxes per line'),
 +            ('round_globally', 'Round globally calculation of taxes '),
              ], string='Tax calculation rounding method',
              help="If you select 'Round per line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes."),
          'sale_tax': fields.many2one("account.tax.template", "Default sale tax"),
          'module_product_email_template': fields.boolean('Send products tools and information at the invoice confirmation',
              help='With this module, link your products to a template to send complete information and tools to your customer.\n'
                   'For instance when invoicing a training, the training agenda and materials will automatically be send to your customers.'),
 +        'module_account_bank_statement_import_ofx': fields.boolean('Import of Bank Statements in .OFX Format',
 +            help='Get your bank statements from you bank and import them in Odoo in .OFX format.\n'
 +                '-that installs the module account_bank_statement_import.'),
 +        'module_account_bank_statement_import_qif': fields.boolean('Import of Bank Statements in .QIF Format.',
 +            help='Get your bank statements from you bank and import them in Odoo in .QIF format.\n'
 +                '-that installs the module account_bank_statement_import_qif.'),
          'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
              implied_group='account.group_proforma_invoices',
              help="Allows you to put invoices in pro-forma state."),
              string="Loss Exchange Rate Account",
              domain="[('type', '=', 'other')]"),
      }
-     def onchange_company_id(self, cr, uid, ids, company_id, context=None):
-         res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
-         if company_id:
-             company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
-             res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False, 
-                                  'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
-         else: 
-             res['value'].update({'income_currency_exchange_account_id': False, 
-                                  'expense_currency_exchange_account_id': False})
-         return res
  
      def _default_company(self, cr, uid, context=None):
          user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
              else:
                  return (time.strftime('%Y-01-01'), time.strftime('%Y-12-31'), 'month')
  
      _defaults = {
          'company_id': _default_company,
          'has_default_company': _default_has_default_company,
                  'default_sale_tax': isinstance(taxes_id, list) and taxes_id[0] or taxes_id,
                  'default_purchase_tax': isinstance(supplier_taxes_id, list) and supplier_taxes_id[0] or supplier_taxes_id,
              })
+             # update gain/loss exchange rate accounts
+             values.update({
+                 'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
+                 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False
+             })
          return {'value': values}
  
      def onchange_chart_template_id(self, cr, uid, ids, chart_template_id, context=None):
@@@ -270,9 -270,13 +270,13 @@@ class account_analytic_account(osv.osv)
          if child_ids:
              #Search all invoice lines not in cancelled state that refer to this analytic account
              inv_line_obj = self.pool.get("account.invoice.line")
-             inv_lines = inv_line_obj.search(cr, uid, ['&', ('account_analytic_id', 'in', child_ids), ('invoice_id.state', '!=', 'cancel')], context=context)
+             inv_lines = inv_line_obj.search(cr, uid, ['&', ('account_analytic_id', 'in', child_ids), ('invoice_id.state', 'not in', ['draft', 'cancel']), ('invoice_id.type', 'in', ['out_invoice', 'out_refund'])], context=context)
              for line in inv_line_obj.browse(cr, uid, inv_lines, context=context):
-                 res[line.account_analytic_id.id] += line.price_subtotal
+                 if line.invoice_id.type == 'out_refund':
+                     res[line.account_analytic_id.id] -= line.price_subtotal
+                 else:
+                     res[line.account_analytic_id.id] += line.price_subtotal
          for acc in self.browse(cr, uid, res.keys(), context=context):
              res[acc.id] = res[acc.id] - (acc.timesheet_ca_invoiced or 0.0)
  
          inv_ids = []
          for account in self.browse(cr, uid, ids, context=context):
              res[account.id] = 0.0
-             line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('to_invoice','!=', False), ('journal_id.type', '=', 'general')], context=context)
+             line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('to_invoice','!=', False), ('journal_id.type', '=', 'general'), ('invoice_id.type', 'in', ['out_invoice', 'out_refund'])], context=context)
              for line in lines_obj.browse(cr, uid, line_ids, context=context):
                  if line.invoice_id not in inv_ids:
                      inv_ids.append(line.invoice_id)
-                     res[account.id] += line.invoice_id.amount_untaxed
+                     if line.invoice_id.type == 'out_refund':
+                         res[account.id] -= line.invoice_id.amount_untaxed
+                     else:
+                         res[account.id] += line.invoice_id.amount_untaxed
          return res
  
      def _remaining_ca_calc(self, cr, uid, ids, name, arg, context=None):
              help="Computes using the formula: (Real Margin / Total Costs) * 100.",
              digits_compute=dp.get_precision('Account')),
          'fix_price_invoices' : fields.boolean('Fixed Price'),
 -        'invoice_on_timesheets' : fields.boolean("On Timesheets"),
          'month_ids': fields.function(_analysis_all, multi='analytic_analysis', type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
          'user_ids': fields.function(_analysis_all, multi='analytic_analysis', type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
          'hours_qtt_est': fields.float('Estimation of Hours to Invoice'),
  
          return True
  
 -    def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
 -        if not invoice_on_timesheets:
 -            return {'value': {'to_invoice': False}}
 -        result = {'value': {'use_timesheets': True}}
 -        try:
 -            to_invoice = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'hr_timesheet_invoice', 'timesheet_invoice_factor1')
 -            result['value']['to_invoice'] = to_invoice[1]
 -        except ValueError:
 -            pass
 -        return result
 -
 -
      def hr_to_invoice_timesheets(self, cr, uid, ids, context=None):
          domain = [('invoice_id','=',False),('to_invoice','!=',False), ('journal_id.type', '=', 'general'), ('account_id', 'in', ids)]
          names = [record.name for record in self.browse(cr, uid, ids, context=context)]
@@@ -900,7 -900,7 +900,7 @@@ class calendar_event(osv.Model)
          'stop_datetime': fields.datetime('End Datetime', states={'done': [('readonly', True)]}, track_visibility='onchange'),  # old date_deadline
          'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
          'description': fields.text('Description', states={'done': [('readonly', True)]}),
 -        'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
 +        'class': fields.selection([('public', 'Everyone'), ('private', 'Only me'), ('confidential', 'Only internal users')], 'Privacy', states={'done': [('readonly', True)]}),
          'location': fields.char('Location', help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}),
          'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}),
  
      def new_invitation_token(self, cr, uid, record, partner_id):
          return uuid.uuid4().hex
  
-     def create_attendees(self, cr, uid, ids, context):
+     def create_attendees(self, cr, uid, ids, context=None):
+         if context is None:
+             context = {}
          user_obj = self.pool['res.users']
          current_user = user_obj.browse(cr, uid, uid, context=context)
          res = {}
  
                  if not current_user.email or current_user.email != partner.email:
                      mail_from = current_user.email or tools.config.get('email_from', False)
-                     if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, att_id, email_from=mail_from, context=context):
-                         self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee %s") % (partner.name,), subtype="calendar.subtype_invitation", context=context)
+                     if not context.get('no_email'):
+                         if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, att_id, email_from=mail_from, context=context):
+                             self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee %s") % (partner.name,), subtype="calendar.subtype_invitation", context=context)
  
              if new_attendees:
                  self.write(cr, uid, [event.id], {'attendee_ids': [(4, att) for att in new_attendees]}, context=context)
diff --combined addons/crm/crm_lead.py
@@@ -208,7 -208,7 +208,7 @@@ class crm_lead(format_address, osv.osv)
              select=True, help="Linked partner (optional). Usually created when converting the lead."),
  
          'id': fields.integer('ID', readonly=True),
 -        'name': fields.char('Subject', required=True, select=1),
 +        'name': fields.char('Opportunity', required=True, select=1),
          'active': fields.boolean('Active', required=False),
          'date_action_last': fields.datetime('Last Action', readonly=1),
          'date_action_next': fields.datetime('Next Action', readonly=1),
          'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
          'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, context=c),
          'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
 -        'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
 +        'priority': lambda *a: crm.AVAILABLE_PRIORITIES[0][0],
          'color': 0,
          'date_last_stage_update': fields.datetime.now,
      }
              'probability': lead.probability,
              'name': lead.name,
              'partner_id': customer and customer.id or False,
-             'user_id': (lead.user_id and lead.user_id.id),
              'type': 'opportunity',
              'date_action': fields.datetime.now(),
              'date_open': fields.datetime.now(),
          lead = self.browse(cr, uid, id, context=context)
          local_context = dict(context)
          local_context.setdefault('default_type', lead.type)
-         local_context.setdefault('default_section_id', lead.section_id)
+         local_context.setdefault('default_section_id', lead.section_id.id)
          if lead.type == 'opportunity':
              default['date_open'] = fields.datetime.now()
          else:
diff --combined addons/event/event.py
@@@ -1,31 -1,45 +1,31 @@@
  # -*- coding: utf-8 -*-
 -##############################################################################
 -#
 -#    OpenERP, Open Source Management Solution
 -#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 -#
 -#    This program is free software: you can redistribute it and/or modify
 -#    it under the terms of the GNU Affero General Public License as
 -#    published by the Free Software Foundation, either version 3 of the
 -#    License, or (at your option) any later version.
 -#
 -#    This program is distributed in the hope that it will be useful,
 -#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 -#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 -#    GNU Affero General Public License for more details.
 -#
 -#    You should have received a copy of the GNU Affero General Public License
 -#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -#
 -##############################################################################
 -from datetime import timedelta
  
  import pytz
  
  from openerp import models, fields, api, _
  from openerp.exceptions import Warning
  
 +
  class event_type(models.Model):
      """ Event Type """
      _name = 'event.type'
      _description = 'Event Type'
  
      name = fields.Char(string='Event Type', required=True)
 -    default_reply_to = fields.Char(string='Default Reply-To',
 +    default_reply_to = fields.Char(
 +        string='Default Reply-To',
          help="The email address of the organizer which is put in the 'Reply-To' of all emails sent automatically at event or registrations confirmation. You can also put your email address of your mail gateway if you use one.")
 -    default_email_event = fields.Many2one('email.template', string='Event Confirmation Email',
 +    default_email_event = fields.Many2one(
 +        'email.template', string='Event Confirmation Email',
          help="It will select this default confirmation event mail value when you choose this event")
 -    default_email_registration = fields.Many2one('email.template', string='Registration Confirmation Email',
 +    default_email_registration = fields.Many2one(
 +        'email.template', string='Registration Confirmation Email',
          help="It will select this default confirmation registration mail value when you choose this event")
 -    default_registration_min = fields.Integer(string='Default Minimum Registration', default=0,
 +    default_registration_min = fields.Integer(
 +        string='Default Minimum Registration', default=0,
          help="It will select this default minimum value when you choose this event")
 -    default_registration_max = fields.Integer(string='Default Maximum Registration', default=0,
 +    default_registration_max = fields.Integer(
 +        string='Default Maximum Registration', default=0,
          help="It will select this default maximum value when you choose this event")
  
  
@@@ -36,65 -50,44 +36,65 @@@ class event_event(models.Model)
      _inherit = ['mail.thread', 'ir.needaction_mixin']
      _order = 'date_begin'
  
 -    name = fields.Char(string='Event Name', translate=True, required=True,
 +    name = fields.Char(
 +        string='Name', translate=True, required=True,
          readonly=False, states={'done': [('readonly', True)]})
 -    user_id = fields.Many2one('res.users', string='Responsible User',
 +    user_id = fields.Many2one(
 +        'res.users', string='Responsible',
          default=lambda self: self.env.user,
          readonly=False, states={'done': [('readonly', True)]})
 -    type = fields.Many2one('event.type', string='Type of Event',
 +    company_id = fields.Many2one(
 +        'res.company', string='Company', change_default=True,
 +        default=lambda self: self.env['res.company']._company_default_get('event.event'),
 +        required=False, readonly=False, states={'done': [('readonly', True)]})
 +    organizer_id = fields.Many2one(
 +        'res.partner', string='Organizer',
 +        default=lambda self: self.env.user.company_id.partner_id)
 +    type = fields.Many2one(
 +        'event.type', string='Category',
          readonly=False, states={'done': [('readonly', True)]})
 -    seats_max = fields.Integer(string='Maximum Available Seats', oldname='register_max',
 +    color = fields.Integer('Kanban Color Index')
 +
 +    # Seats and computation
 +    seats_max = fields.Integer(
 +        string='Maximum Available Seats', oldname='register_max',
          readonly=True, states={'draft': [('readonly', False)]},
          help="You can for each event define a maximum registration level. If you have too much registrations you are not able to confirm your event. (put 0 to ignore this rule )")
 -    seats_min = fields.Integer(string='Minimum Reserved Seats', oldname='register_min',
 +    seats_availability = fields.Selection(
 +        [('limited', 'Limited'), ('unlimited', 'Unlimited')],
 +        'Available Seat', required=True, default='unlimited')
 +    seats_min = fields.Integer(
 +        string='Minimum Reserved Seats', oldname='register_min',
          readonly=True, states={'draft': [('readonly', False)]},
          help="You can for each event define a minimum registration level. If you do not enough registrations you are not able to confirm your event. (put 0 to ignore this rule )")
 -
 -    seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats',
 +    seats_reserved = fields.Integer(
 +        oldname='register_current', string='Reserved Seats',
          store=True, readonly=True, compute='_compute_seats')
 -    seats_available = fields.Integer(oldname='register_avail', string='Available Seats',
 +    seats_available = fields.Integer(
 +        oldname='register_avail', string='Available Seats',
          store=True, readonly=True, compute='_compute_seats')
 -    seats_unconfirmed = fields.Integer(oldname='register_prospect', string='Unconfirmed Seat Reservations',
 +    seats_unconfirmed = fields.Integer(
 +        oldname='register_prospect', string='Unconfirmed Seat Reservations',
          store=True, readonly=True, compute='_compute_seats')
 -    seats_used = fields.Integer(oldname='register_attended', string='Number of Participations',
 +    seats_used = fields.Integer(
 +        oldname='register_attended', string='Number of Participations',
          store=True, readonly=True, compute='_compute_seats')
  
      @api.multi
 -    @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
 +    @api.depends('seats_max', 'registration_ids.state')
      def _compute_seats(self):
          """ Determine reserved, available, reserved but unconfirmed and used seats. """
          # initialize fields to 0
          for event in self:
 -            event.seats_unconfirmed = event.seats_reserved = event.seats_used = 0
 +            event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0
          # aggregate registrations by event and by state
          if self.ids:
              state_field = {
                  'draft': 'seats_unconfirmed',
 -                'open':'seats_reserved',
 +                'open': 'seats_reserved',
                  'done': 'seats_used',
              }
 -            query = """ SELECT event_id, state, sum(nb_register)
 +            query = """ SELECT event_id, state, count(event_id)
                          FROM event_registration
                          WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
                          GROUP BY event_id, state
                  event[state_field[state]] += num
          # compute seats_available
          for event in self:
 -            event.seats_available = \
 -                event.seats_max - (event.seats_reserved + event.seats_used) \
 -                if event.seats_max > 0 else 0
 +            if event.seats_max > 0:
 +                event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used)
  
 -    registration_ids = fields.One2many('event.registration', 'event_id', string='Registrations',
 +    # Registration fields
 +    registration_ids = fields.One2many(
 +        'event.registration', 'event_id', string='Attendees',
          readonly=False, states={'done': [('readonly', True)]})
 -    count_registrations = fields.Integer(string='Registrations',
 -        compute='_count_registrations')
 +    count_registrations = fields.Integer(string='Registrations', compute='_count_registrations')
 +
 +    @api.one
 +    @api.depends('registration_ids')
 +    def _count_registrations(self):
 +        self.count_registrations = len(self.registration_ids)
  
 -    date_begin = fields.Datetime(string='Start Date', required=True,
 +    # Date fields
 +    date_tz = fields.Selection('_tz_get', string='Timezone', default=lambda self: self.env.user.tz)
 +    date_begin = fields.Datetime(
 +        string='Start Date', required=True,
          readonly=True, states={'draft': [('readonly', False)]})
 -    date_end = fields.Datetime(string='End Date', required=True,
 +    date_end = fields.Datetime(
 +        string='End Date', required=True,
          readonly=True, states={'draft': [('readonly', False)]})
 +    date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
 +    date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
  
      @api.model
      def _tz_get(self):
          return [(x, x) for x in pytz.all_timezones]
  
 -    date_tz = fields.Selection('_tz_get', string='Timezone',
 -                        default=lambda self: self._context.get('tz', 'UTC'))
 -
      @api.one
      @api.depends('date_tz', 'date_begin')
      def _compute_date_begin_tz(self):
          else:
              self.date_end_located = False
  
+     @api.one
+     @api.depends('address_id')
+     def _compute_country(self):
+         self.country_id = self.address_id.country_id
 -    date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
 -    date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
 -
      state = fields.Selection([
 -            ('draft', 'Unconfirmed'),
 -            ('cancel', 'Cancelled'),
 -            ('confirm', 'Confirmed'),
 -            ('done', 'Done')
 -        ], string='Status', default='draft', readonly=True, required=True, copy=False,
 +        ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
 +        ('confirm', 'Confirmed'), ('done', 'Done')],
 +        string='Status', default='draft', readonly=True, required=True, copy=False,
          help="If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'.")
 +    auto_confirm = fields.Boolean(string='Auto Confirmation Activated', compute='_compute_auto_confirm')
 +
 +    @api.one
 +    def _compute_auto_confirm(self):
 +        self.auto_confirm = self.env['ir.values'].get_default('marketing.config.settings', 'auto_confirmation')
 +
 +    # Mailing
      email_registration_id = fields.Many2one(
          'email.template', string='Registration Confirmation Email',
          domain=[('model', '=', 'event.registration')],
          'email.template', string='Event Confirmation Email',
          domain=[('model', '=', 'event.registration')],
          help="If you set an email template, each participant will receive this email announcing the confirmation of the event.")
 -    reply_to = fields.Char(string='Reply-To Email',
 -        readonly=False, states={'done': [('readonly', True)]},
 +    reply_to = fields.Char(
 +        string='Reply-To Email', readonly=False, states={'done': [('readonly', True)]},
          help="The email address of the organizer is likely to be put here, with the effect to be in the 'Reply-To' of the mails sent automatically at event or registrations confirmation. You can also put the email address of your mail gateway if you use one.")
 -    address_id = fields.Many2one('res.partner', string='Location',
 -        default=lambda self: self.env.user.company_id.partner_id,
 +    address_id = fields.Many2one(
 +        'res.partner', string='Location', default=lambda self: self.env.user.company_id.partner_id,
          readonly=False, states={'done': [('readonly', True)]})
-     country_id = fields.Many2one(
-         'res.country', string='Country', related='address_id.country_id',
-         store=True, readonly=False, states={'done': [('readonly', True)]})
+     country_id = fields.Many2one('res.country', string='Country',
+         store=True, compute='_compute_country')
 -    description = fields.Html(string='Description', oldname='note', translate=True,
 +    description = fields.Html(
 +        string='Description', oldname='note', translate=True,
          readonly=False, states={'done': [('readonly', True)]})
 -    company_id = fields.Many2one('res.company', string='Company', change_default=True,
 -        default=lambda self: self.env['res.company']._company_default_get('event.event'),
 -        required=False, readonly=False, states={'done': [('readonly', True)]})
 -    organizer_id = fields.Many2one('res.partner', string='Organizer',
 -        default=lambda self: self.env.user.company_id.partner_id)
 -
 -    is_subscribed = fields.Boolean(string='Subscribed',
 -        compute='_compute_subscribe')
 -
 -    @api.one
 -    @api.depends('registration_ids')
 -    def _count_registrations(self):
 -        self.count_registrations = len(self.registration_ids)
 -
 -    @api.one
 -    @api.depends('registration_ids.user_id', 'registration_ids.state')
 -    def _compute_subscribe(self):
 -        """ Determine whether the current user is already subscribed to any event in `self` """
 -        user = self.env.user
 -        self.is_subscribed = any(
 -            reg.user_id == user and reg.state in ('open', 'done')
 -            for reg in self.registration_ids
 -        )
  
      @api.multi
      @api.depends('name', 'date_begin', 'date_end')
          if self.date_end < self.date_begin:
              raise Warning(_('Closing Date cannot be set before Beginning Date.'))
  
 +    @api.model
 +    def create(self, vals):
 +        res = super(event_event, self).create(vals)
 +        if res.auto_confirm:
 +            res.confirm_event()
 +        return res
 +
      @api.one
      def button_draft(self):
          self.state = 'draft'
              if event_reg.state == 'done':
                  raise Warning(_("You have already set a registration for this event as 'Attended'. Please reset it to draft if you want to cancel this event."))
          self.registration_ids.write({'state': 'cancel'})
 -        self.state = 'cancel'                
 +        self.state = 'cancel'
  
      @api.one
      def button_done(self):
          """ Confirm Event and send confirmation email to all register peoples """
          self.confirm_event()
  
 -    @api.one
 -    def subscribe_to_event(self):
 -        """ Subscribe the current user to a given event """
 -        user = self.env.user
 -        num_of_seats = int(self._context.get('ticket', 1))
 -        regs = self.registration_ids.filtered(lambda reg: reg.user_id == user)
 -        # the subscription is done as SUPERUSER_ID because in case we share the
 -        # kanban view, we want anyone to be able to subscribe
 -        if not regs:
 -            regs = regs.sudo().create({
 -                'event_id': self.id,
 -                'email': user.email,
 -                'name':user.name,
 -                'user_id': user.id,
 -                'nb_register': num_of_seats,
 -            })
 -        else:
 -            regs.write({'nb_register': num_of_seats})
 -        regs.sudo().confirm_registration()
 -
 -    @api.one
 -    def unsubscribe_to_event(self):
 -        """ Unsubscribe the current user from a given event """
 -        # the unsubscription is done as SUPERUSER_ID because in case we share
 -        # the kanban view, we want anyone to be able to unsubscribe
 -        user = self.env.user
 -        regs = self.sudo().registration_ids.filtered(lambda reg: reg.user_id == user)
 -        regs.button_reg_cancel()
 -
      @api.onchange('type')
      def _onchange_type(self):
          if self.type:
              self.seats_min = self.type.default_registration_min
              self.seats_max = self.type.default_registration_max
  
 -    @api.onchange('date_begin')
 -    def _onchange_date_begin(self):
 -        if self.date_begin and not self.date_end:
 -            date_begin = fields.Datetime.from_string(self.date_begin)
 -            self.date_end = fields.Datetime.to_string(date_begin + timedelta(hours=1))
 +    @api.multi
 +    def action_event_registration_report(self):
 +        res = self.env['ir.actions.act_window'].for_xml_id('event', 'action_report_event_registration')
 +        res['context'] = {
 +            "search_default_event_id": self.id,
 +            "group_by": ['create_date:day'],
 +        }
 +        return res
  
  
  class event_registration(models.Model):
      _name = 'event.registration'
 -    _description = 'Event Registration'
 +    _description = 'Attendee'
      _inherit = ['mail.thread', 'ir.needaction_mixin']
      _order = 'name, create_date desc'
  
 -    origin = fields.Char(string='Source Document', readonly=True,
 -        help="Reference of the sales order which created the registration")
 -    nb_register = fields.Integer(string='Number of Participants', required=True, default=1,
 +    origin = fields.Char(
 +        string='Source Document', readonly=True,
 +        help="Reference of the document that created the registration, for example a sale order")
 +    event_id = fields.Many2one(
 +        'event.event', string='Event', required=True,
          readonly=True, states={'draft': [('readonly', False)]})
 -    event_id = fields.Many2one('event.event', string='Event', required=True,
 -        readonly=True, states={'draft': [('readonly', False)]})
 -    partner_id = fields.Many2one('res.partner', string='Partner',
 +    partner_id = fields.Many2one(
 +        'res.partner', string='Contact',
          states={'done': [('readonly', True)]})
      date_open = fields.Datetime(string='Registration Date', readonly=True)
      date_closed = fields.Datetime(string='Attended Date', readonly=True)
 -    reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to',
 -        readonly=True)
 -    log_ids = fields.One2many('mail.message', 'res_id', string='Logs',
 -        domain=[('model', '=', _name)])
 -    event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin',
 -        readonly=True)
 -    event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end',
 -        readonly=True)
 -    user_id = fields.Many2one('res.users', string='User', states={'done': [('readonly', True)]})
 -    company_id = fields.Many2one('res.company', string='Company', related='event_id.company_id',
 -        store=True, readonly=True, states={'draft':[('readonly', False)]})
 +    reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to', readonly=True)
 +    event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
 +    event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
 +    company_id = fields.Many2one(
 +        'res.company', string='Company', related='event_id.company_id',
 +        store=True, readonly=True, states={'draft': [('readonly', False)]})
      state = fields.Selection([
 -            ('draft', 'Unconfirmed'),
 -            ('cancel', 'Cancelled'),
 -            ('open', 'Confirmed'),
 -            ('done', 'Attended'),
 -        ], string='Status', default='draft', readonly=True, copy=False)
 +        ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
 +        ('open', 'Confirmed'), ('done', 'Attended')],
 +        string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
      email = fields.Char(string='Email')
      phone = fields.Char(string='Phone')
 -    name = fields.Char(string='Name', select=True)
 +    name = fields.Char(string='Attendee Name', select=True)
  
      @api.one
 -    @api.constrains('event_id', 'state', 'nb_register')
 +    @api.constrains('event_id', 'state')
      def _check_seats_limit(self):
 -        if self.event_id.seats_max and \
 -            self.event_id.seats_available < (self.nb_register if self.state == 'draft' else 0):
 -                raise Warning(_('No more available seats.'))
 +        if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
 +            raise Warning(_('No more seats available for this event.'))
 +
 +    @api.one
 +    def _check_auto_confirmation(self):
 +        if self._context.get('registration_force_draft'):
 +            return False
 +        if self.event_id and self.event_id.state == 'confirm' and self.event_id.auto_confirm and self.event_id.seats_available:
 +            return True
 +        return False
 +
 +    @api.model
 +    def create(self, vals):
 +        res = super(event_registration, self).create(vals)
 +        if res._check_auto_confirmation()[0]:
 +            res.sudo().confirm_registration()
 +        return res
  
      @api.one
      def do_draft(self):
          self.event_id.message_post(
              body=_('New registration confirmed: %s.') % (self.name or ''),
              subtype="event.mt_event_registration")
 -        self.message_post(body=_('Event Registration confirmed.'))
          self.state = 'open'
  
      @api.one
          else:
              template = self.event_id.email_registration_id
              if template:
 -                mail_message = template.send_mail(self.id)
 +                template.send_mail(self.id)
  
      @api.one
      def mail_user_confirm(self):
          """Send email to user when the event is confirmed """
          template = self.event_id.email_confirmation_id
          if template:
 -            mail_message = template.send_mail(self.id)
 +            template.send_mail(self.id)
  
      @api.onchange('partner_id')
      def _onchange_partner(self):
              contact_id = self.partner_id.address_get().get('default', False)
              if contact_id:
                  contact = self.env['res.partner'].browse(contact_id)
 -                self.name = contact.name
 -                self.email = contact.email
 -                self.phone = contact.phone
 -
 -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 +                self.name = self.name or contact.name
 +                self.email = self.email or contact.email
 +                self.phone = self.phone or contact.phone
  #
  ##############################################################################
  
 +
 +import calendar
  import datetime
 +from datetime import date
  import math
  import time
  from operator import attrgetter
@@@ -114,14 -111,13 +114,14 @@@ class hr_holidays(osv.osv)
      _inherit = ['mail.thread', 'ir.needaction_mixin']
      _track = {
          'state': {
 +            'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
 +            'hr_holidays.mt_holidays_first_validated': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate1',
              'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate',
              'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'refuse',
          },
      }
  
 -    def _employee_get(self, cr, uid, context=None):        
 +    def _employee_get(self, cr, uid, context=None):
          emp_id = context.get('default_employee_id', False)
          if emp_id:
              return emp_id
              \nThe status is \'To Approve\', when holiday request is confirmed by user.\
              \nThe status is \'Refused\', when holiday request is refused by manager.\
              \nThe status is \'Approved\', when holiday request is approved by manager.'),
 +        'payslip_status': fields.boolean(string='Payslip Status',
 +            help='Check this field when the leave has been taken into account in the payslip.'),
 +        'report_note': fields.text('HR Comments'),
          'user_id':fields.related('employee_id', 'user_id', type='many2one', relation='res.users', string='User', store=True),
          'date_from': fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, select=True, copy=False),
          'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, copy=False),
          'state': 'confirm',
          'type': 'remove',
          'user_id': lambda obj, cr, uid, context: uid,
 -        'holiday_type': 'employee'
 +        'holiday_type': 'employee',
 +        'payslip_status': False,
      }
      _constraints = [
          (_check_date, 'You can not have 2 leaves that overlaps on same day!', ['date_from','date_to']),
          """
          Update the number_of_days.
          """
 -
          # date_to has to be greater than date_from
          if (date_from and date_to) and (date_from > date_to):
              raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.'))
              result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1
          else:
              result['value']['number_of_days_temp'] = 0
 -
          return result
  
      def create(self, cr, uid, values, context=None):
          obj_emp = self.pool.get('hr.employee')
          ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
          manager = ids2 and ids2[0] or False
 -        self.holidays_first_validate_notificate(cr, uid, ids, context=context)
 -        return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
 +        return self.write(cr, uid, ids, {'state': 'validate1', 'manager_id': manager}, context=context)
  
      def holidays_validate(self, cr, uid, ids, context=None):
          obj_emp = self.pool.get('hr.employee')
          ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
          manager = ids2 and ids2[0] or False
 -        self.write(cr, uid, ids, {'state':'validate'})
 +        self.write(cr, uid, ids, {'state': 'validate'}, context=context)
          data_holiday = self.browse(cr, uid, ids)
          for record in data_holiday:
              if record.double_validation:
                  if record.user_id and record.user_id.partner_id:
                      meeting_vals['partner_ids'] = [(4,record.user_id.partner_id.id)]
                      
-                 meeting_id = meeting_obj.create(cr, uid, meeting_vals)
+                 ctx_no_email = dict(context or {}, no_email=True)
+                 meeting_id = meeting_obj.create(cr, uid, meeting_vals, context=ctx_no_email)
                  self._create_resource_leave(cr, uid, [record], context=context)
                  self.write(cr, uid, ids, {'meeting_id': meeting_id})
              elif record.holiday_type == 'category':
          return True
  
      def holidays_cancel(self, cr, uid, ids, context=None):
-         meeting_obj = self.pool.get('calendar.event')
          for record in self.browse(cr, uid, ids):
              # Delete the meeting
              if record.meeting_id:
                                  'Please verify also the leaves waiting for validation.'))
          return True
  
 -    # -----------------------------
 -    # OpenChatter and notifications
 -    # -----------------------------
 -
 -    def _needaction_domain_get(self, cr, uid, context=None):
 -        emp_obj = self.pool.get('hr.employee')
 -        empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
 -        dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
 -        # if this user is a hr.manager, he should do second validations
 -        if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
 -            dom = ['|'] + dom + [('state', '=', 'validate1')]
 -        return dom
 +    def toggle_payslip_status(self, cr, uid, ids, context=None):
 +        ids_to_set_true = self.search(cr, uid, [('id', 'in', ids), ('payslip_status', '=', False)], context=context)
 +        ids_to_set_false = list(set(ids) - set(ids_to_set_true))
 +        return self.write(cr, uid, ids_to_set_true, {'payslip_status': True}, context=context) and self.write(cr, uid, ids_to_set_false, {'payslip_status': False}, context=context)
  
 -    def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
 -        for obj in self.browse(cr, uid, ids, context=context):
 -            self.message_post(cr, uid, [obj.id],
 -                _("Request approved, waiting second validation."), context=context)
  
  class resource_calendar_leaves(osv.osv):
      _inherit = "resource.calendar.leaves"
      }
  
  
 -
 -class hr_employee(osv.osv):
 -    _inherit="hr.employee"
 +class hr_employee(osv.Model):
 +    _inherit = "hr.employee"
  
      def create(self, cr, uid, vals, context=None):
          # don't pass the value of remaining leave if it's 0 at the creation time, otherwise it will trigger the inverse
          return result
  
      def _leaves_count(self, cr, uid, ids, field_name, arg, context=None):
 +        res = {}
          Holidays = self.pool['hr.holidays']
 -        return {
 -            employee_id: Holidays.search_count(cr,uid, [('employee_id', '=', employee_id)], context=context) 
 -            for employee_id in ids
 -        }
 +        date_begin = date.today().replace(day=1)
 +        date_end = date_begin.replace(day=calendar.monthrange(date_begin.year, date_begin.month)[1])
 +        for employee_id in ids:
 +            leaves = Holidays.search_count(cr, uid, [('employee_id', '=', employee_id), ('type', '=', 'remove')], context=context)
 +            approved_leaves = Holidays.search_count(cr, uid, [('employee_id', '=', employee_id), ('type', '=', 'remove'), ('date_from', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('date_from', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('state', '=', 'validate'), ('payslip_status', '=', False)], context=context)
 +            res[employee_id] = {'leaves_count': leaves, 'approved_leaves_count': approved_leaves}
 +        return res
  
      _columns = {
          'remaining_leaves': fields.function(_get_remaining_days, string='Remaining Legal Leaves', fnct_inv=_set_remaining_days, type="float", help='Total number of legal leaves allocated to this employee, change this value to create allocation/leave request. Total based on all the leave types without overriding limit.'),
 -        'current_leave_state': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
 +        'current_leave_state': fields.function(
 +            _get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
              selection=[('draft', 'New'), ('confirm', 'Waiting Approval'), ('refuse', 'Refused'),
 -            ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
 -        'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type",type='many2one', relation='hr.holidays.status'),
 +                       ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
 +        'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type", type='many2one', relation='hr.holidays.status'),
          'leave_date_from': fields.function(_get_leave_status, multi='leave_status', type='date', string='From Date'),
          'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'),
 -        'leaves_count': fields.function(_leaves_count, type='integer', string='Leaves'),
 -
 +        'leaves_count': fields.function(_leaves_count, multi='_leaves_count', type='integer', string='Number of Leaves (current month)'),
 +        'approved_leaves_count': fields.function(_leaves_count, multi='_leaves_count', type='integer', string='Approved Leaves not in Payslip', help="These leaves are approved but not taken into account for payslip"),
      }
 -
 -
 -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --combined addons/mrp/mrp.py
@@@ -159,7 -159,7 +159,7 @@@ class mrp_bom(osv.osv)
          'name': fields.char('Name'),
          'code': fields.char('Reference', size=16),
          'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the bills of material without removing it."),
 -        'type': fields.selection([('normal', 'Normal'), ('phantom', 'Set')], 'BoM Type', required=True,
 +        'type': fields.selection([('normal','Manufacture this product as a normal bill of material'),('phantom','Sell and ship this product as a set of components(phantom)')], 'BoM Type', required=True,
                  help= "Set: When processing a sales order for this product, the delivery order will contain the raw materials, instead of the finished product."),
          'position': fields.char('Internal Reference', help="Reference to a position in an external plan."),
          'product_tmpl_id': fields.many2one('product.template', 'Product', domain="[('type', '!=', 'service')]", required=True),
  
          for bom_line_id in bom.bom_line_ids:
              if bom_line_id.date_start and bom_line_id.date_start > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) or \
-                 bom_line_id.date_stop and bom_line_id.date_stop > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
+                 bom_line_id.date_stop and bom_line_id.date_stop < time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
                      continue
              # all bom_line_id variant values must be in the product
              if bom_line_id.attribute_value_ids:
@@@ -406,14 -406,6 +406,14 @@@ class mrp_bom_line(osv.osv)
              'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
      ]
  
 +    def create(self, cr, uid, values, context=None):
 +        if context is None:
 +            context = {}
 +        product_obj = self.pool.get('product.product')
 +        if 'product_id' in values and not 'product_uom' in values:
 +            values['product_uom'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
 +        return super(mrp_bom_line, self).create(cr, uid, values, context=context)
 +
      def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
          res = {'value': {}}
          if not product_uom or not product_id:
@@@ -602,14 -594,6 +602,14 @@@ class mrp_production(osv.osv)
          (_check_qty, 'Order quantity cannot be negative or zero!', ['product_qty']),
      ]
  
 +    def create(self, cr, uid, values, context=None):
 +        if context is None:
 +            context = {}
 +        product_obj = self.pool.get('product.product')
 +        if 'product_id' in values and not 'product_uom' in values:
 +            values['product_uom'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
 +        return super(mrp_production, self).create(cr, uid, values, context=context)
 +
      def unlink(self, cr, uid, ids, context=None):
          for production in self.browse(cr, uid, ids, context=context):
              if production.state not in ('draft', 'cancel'):
          <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>
                </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">
                          <field name="expense_pdt"/>
                      </group>
                  </group>
+             </field>
+         </record>
+         <record id="product_template_form_view_inherit_ean" model="ir.ui.view">
+             <field name="name">product.template.only.form.inherit.ean</field>
+             <field name="model">product.template</field>
+             <field name="inherit_id" ref="product.product_template_only_form_view"/>
+             <field name="arch" type="xml">
+                 <field name="ean13" position="after">
+                     <button colspan="2" name="%(action_edit_ean)d" type="action" string="Set a Custom EAN"
+                             attrs="{'invisible': [('product_variant_count', '>', 1)]}" class="oe_link oe_edit_only"/>
+                 </field>
+             </field>
+         </record>
+         <record id="product_normal_form_view_inherit_ean" model="ir.ui.view">
+             <field name="name">product.form.inherit.ean</field>
+             <field name="model">product.product</field>
+             <field name="inherit_id" ref="product.product_normal_form_view"/>
+             <field name="arch" type="xml">
                  <field name="ean13" position="after">
                      <button colspan="2" name="%(action_edit_ean)d" type="action" string="Set a Custom EAN" class="oe_link oe_edit_only"/>
                  </field>
          </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>
 +
@@@ -294,12 -294,14 +294,12 @@@ function openerp_pos_models(instance, m
              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(){
          // 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(){
              
              for(var id in details){
                  if(details.hasOwnProperty(id)){
-                     fulldetails.push({amount: details[id], tax: taxes_by_id[id]});
+                     fulldetails.push({amount: details[id], tax: taxes_by_id[id], name: taxes_by_id[id].name});
                  }
              }
  
                  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);
              }
          },
  
      module.ReceiptScreenWidget = module.ScreenWidget.extend({
          template: 'ReceiptScreenWidget',
 -
 -        show_numpad:     true,
 -        show_leftpane:   true,
 +        show_numpad:     false,
 +        show_leftpane:   false,
  
          show: function(){
              this._super();
              var self = this;
  
              this.refresh();
              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() {
              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)   
 -                }
 -                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);
 +            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 = '+';
                  }
 -            };
  
 -            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);
 -                }
 -            };
 +                self.payment_input(key);
  
 -            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(2);
 -                        }
 -                        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);
 -            }
          },
      });
 +
  }
@@@ -1,7 -1,5 +1,7 @@@
  function openerp_pos_basewidget(instance, module){ //module is instance.point_of_sale
  
 +    var round_pr = instance.web.round_precision
 +
      // This is a base class for all Widgets in the POS. It exposes relevant data to the 
      // templates : 
      // - widget.currency : { symbol: '$' | '€' | ..., position: 'before' | 'after }
  
              var decimals = Math.max(0,Math.ceil(Math.log(1.0 / this.currency.rounding) / Math.log(10)));
  
 +            this.format_currency_no_symbol = function(amount){
 +                amount = round_pr(amount,this.currency.rounding);
 +                amount = amount.toFixed(decimals);
 +                return amount;
 +            };
 +
              this.format_currency = function(amount){
                  if(typeof amount === 'number'){
                      amount = Math.round(amount*100)/100;
                      amount = amount.toFixed(decimals);
                  }
                  if(this.currency.position === 'after'){
-                     return amount + ' ' + this.currency.symbol;
+                     return amount + ' ' + (this.currency.symbol || '');
                  }else{
-                     return this.currency.symbol + ' ' + amount;
+                     return (this.currency.symbol || '') + ' ' + amount;
                  }
 -            }
 +            };
  
          },
          show: function(){
                              <div class='subwindow-container'>
                                  <div class='subwindow-container-fix pads'>
                                      <div class="control-buttons oe_hidden"></div>
 -                                    <div class="placeholder-PaypadWidget"></div>
 +                                    <div class="placeholder-ActionpadWidget"></div>
                                      <div class="placeholder-NumpadWidget"></div>
                                  </div>
                              </div>
                          </div>
  
 -                        <div class='subwindow collapsed'>
 -                            <div class='subwindow-container'>
 -                                <div class='subwindow-container-fix'>
 -                                    <div class='placeholder-LeftActionBar'></div>
 -                                </div>
 -                            </div>
 -                        </div>
                      </div>
                  </div>
  
                                  </div>
                              </div>
                          </div>
 -
 -                        <div class='subwindow collapsed'>
 -                            <div class='subwindow-container'>
 -                                <div class='subwindow-container-fix'>
 -                                    <div class='placeholder-RightActionBar'></div>
 -                                </div>
 -                            </div>
 -                        </div>
                      </div>
                  </div>
  
          <div>There are pending operations that could not be saved into the database, are you sure you want to exit?</div>
      </t>
  
 -    <t t-name="PaypadWidget">
 -        <div class="paypad touch-scrollable">
 +    <t t-name="ActionpadWidget">
 +        <div class='actionpad'>
 +            <button class='button set-customer'>
 +                <i class='fa fa-user' /> Set Customer
 +            </button>
 +            <button class="button pay">
 +                <i class='fa fa-chevron-right' /> Payment
 +            </button>
          </div>
      </t>
  
          </div>
      </t>
  
 -        
 +    <t t-name="PaymentScreen-Paymentlines">
 +        <t t-if="!paymentlines.length">
 +            <div class='paymentlines-empty'>
 +                <div class='total'>
 +                    <t t-esc="widget.format_currency(order.getTotalTaxIncluded())"/>
 +                </div>
 +                <div class='message'>
 +                    Please select a payment method. 
 +                </div>
 +            </div>
 +        </t>
  
 +        <t t-if="paymentlines.length">
 +            <table class='paymentlines'>
 +                <colgroup>
 +                    <col class='due' />
 +                    <col class='tendered' />
 +                    <col class='change' />
 +                    <col class='method' />
 +                    <col class='controls' />
 +                </colgroup>
 +                <thead>
 +                    <tr class='label'>
 +                        <th>Due</th>
 +                        <th>Tendered</th>
 +                        <th>Change</th>
 +                        <th>Method</th>
 +                        <th></th>
 +                    </tr>
 +                </thead>
 +                <tbody>
 +                    <t t-foreach='paymentlines' t-as='line'>
 +                        <t t-if='line.selected'>
 +                            <tr class='paymentline selected'>
 +                                <td class='col-due'> <t t-esc='widget.format_currency_no_symbol(order.getDueLeft(line))' /> </td>
 +                                <td class='col-tendered edit'> 
 +                                    <t t-esc='widget.inputbuffer' />
 +                                    <!-- <t t-esc='line.get_amount()' /> -->
 +                                </td>
 +                                <t t-if='order.getChange(line)'>
 +                                    <td class='col-change highlight' > 
 +                                        <t t-esc='widget.format_currency_no_symbol(order.getChange(line))' />
 +                                    </td>
 +                                </t>
 +                                <t t-if='!order.getChange(line)'>
 +                                    <td class='col-change' ></td>
 +                                </t>
 +                                    
 +                                <td class='col-name' > <t t-esc='line.name' /> </td>
 +                                <td class='delete-button' t-att-data-cid='line.cid'> <i class='fa fa-times-circle' /> </td>
 +                            </tr>
 +                        </t>
 +                        <t t-if='!line.selected'>
 +                            <tr class='paymentline' t-att-data-cid='line.cid'>
 +                                <td class='col-due'> <t t-esc='widget.format_currency_no_symbol(order.getDueLeft(line))' /> </td>
 +                                <td class='col-tendered'> <t t-esc='widget.format_currency_no_symbol(line.get_amount())' /> </td>
 +                                <td class='col-change'> 
 +                                    <t t-if='order.getChange(line)'>
 +                                        <t t-esc='widget.format_currency_no_symbol(order.getChange(line))' />
 +                                     </t>
 +                                </td>
 +                                <td class='col-name'> <t t-esc='line.name' /> </td>
 +                                <td class='delete-button' t-att-data-cid='line.cid'> <i class='fa fa-times-circle' /> </td>
 +                            </tr>
 +                        </t>
 +                    </t>
 +                </tbody>
 +            </table>
 +        </t>
 +
 +    </t>
 +
 +    <t t-name="PaymentScreen-Numpad">
 +        <div class="numpad">
 +            <button class="input-button number-char" data-action='1'>1</button>
 +            <button class="input-button number-char" data-action='2'>2</button>
 +            <button class="input-button number-char" data-action='3'>3</button>
 +            <button class="mode-button" data-action='+10'>+10</button>
 +            <br />
 +            <button class="input-button number-char" data-action='4'>4</button>
 +            <button class="input-button number-char" data-action='5'>5</button>
 +            <button class="input-button number-char" data-action='6'>6</button>
 +            <button class="mode-button" data-action='+20'>+20</button>
 +            <br />
 +            <button class="input-button number-char" data-action='7'>7</button>
 +            <button class="input-button number-char" data-action='8'>8</button>
 +            <button class="input-button number-char" data-action='9'>9</button>
 +            <button class="mode-button" data-action='+50'>+50</button>
 +            <br />
 +            <button class="input-button numpad-char" data-action='CLEAR' >C</button>
 +            <button class="input-button number-char" data-action='0'>0</button>
 +            <button class="input-button number-char" data-action='.'>.</button>
 +            <button class="input-button numpad-backspace" data-action='BACKSPACE' >
 +                <img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
 +            </button>
 +            <br />
 +        </div>
 +    </t>
 +
 +    <t t-name="PaymentScreen-Paymentmethods">
 +        <div class='paymentmethods'>
 +            <t t-foreach="widget.pos.cashregisters" t-as="cashregister">
 +                <div class="paymentmethod" t-att-data-id="cashregister.journal_id[0]">
 +                    <t t-esc="cashregister.journal_id[1]" />
 +                </div>
 +            </t>
 +        </div>
 +    </t>
 +        
      <t t-name="PaymentScreenWidget">
 -        <div class="payment-screen screen touch-scrollable">
 -            <div class="pos-payment-container">
 -                <div class='payment-due-total'></div>
 -                <div class='payment-lines'></div>
 -                <div class='payment-info'>
 -                    <div class="infoline">
 -                        <span class='left-block'>
 -                            Paid:
 -                        </span>
 -                        <span class="right-block payment-paid-total"></span>
 -                    </div>
 -                    <div class="infoline">
 -                        <span class='left-block'>
 -                            Remaining:
 -                        </span>
 -                        <span class="right-block payment-remaining"></span>
 +        <div class='payment-screen screen'>
 +            <div class='screen-content'>
 +                <div class='top-content'>
 +                    <span class='button back'>
 +                        <i class='fa fa-angle-double-left'></i>
 +                        Back
 +                    </span>
 +                    <h1>Payment</h1>
 +                    <span class='button next'>
 +                        Validate
 +                        <i class='fa fa-angle-double-right'></i>
 +                    </span>
 +                </div>
 +                <div class='left-content pc40 touch-scrollable scrollable-y'>
 +
 +                    <div class='paymentmethods-container'>
                      </div>
 -                    <div class="infoline bigger" >
 -                        <span class='left-block'>
 -                            Change:
 -                        </span>
 -                        <span class="right-block payment-change"></span>
 +
 +                </div>
 +                <div class='right-content pc60 touch-scrollable scrollable-y'>
 +
 +                    <section class='paymentlines-container'>
 +                    </section>
 +
 +                    <section class='payment-numpad'>
 +                    </section>
 +
 +                    <div class='payment-buttons'>
 +                        <t t-if='widget.pos.config.iface_invoicing'>
 +                            <div t-attf-class='button js_invoice #{ widget.pos.get_order().is_to_invoice() ? "highlight" : ""} '>
 +                                <i class='fa fa-file-text-o' /> Invoice
 +                            </div>
 +                        </t>
 +                        <t t-if='widget.pos.config.iface_cashdrawer'>
 +                            <div class='button js_invoice'>
 +                                <i class='fa fa-archive' /> Open Cashbox
 +                            </div>
 +                        </t>
                      </div>
 +
 +
                  </div>
              </div>
          </div>
 +
      </t>
  
      <t t-name="ReceiptScreenWidget">
 -        <div class="receipt-screen screen touch-scrollable" >
 -            <div class="pos-step-container">
 -                <div class="pos-receipt-container">
 +        <div class='receipt-screen screen'>
 +            <div class='screen-content'>
 +                <div class='top-content'>
 +                    <h1>Receipt</h1>
 +                    <span class='button next'>
 +                        Next Order
 +                        <i class='fa fa-angle-double-right'></i>
 +                    </span>
 +                </div>
 +                <div class="centered-content">
 +                    <div class="button print">
 +                        <i class='fa fa-print'></i> Print
 +                    </div>
 +                    <div class="pos-receipt-container">
 +                    </div>
                  </div>
              </div>
          </div>
          </receipt>
      </t>
  
 +    <t t-name="FullscreenPopupWidget">
 +        <div class="modal-dialog">
 +            <div class="popup popup-fullscreen">
 +                <p class="message">Fullscreen Setup</p>
 +
 +                <t t-if='widget.ismobile()'>
 +                    <p class="comment">
 +                        The best way to make the point of sale fullscreen on mobile
 +                        devices is to add the point of sale to your home screen. On 
 +                        iPhone and iPad this is done by tapping <img src='/point_of_sale/static/src/img/ios-share-icon.png' />
 +                        and then <i>Add to Homescreen</i>
 +                    </p>
 +                    <p class='comment'>
 +                        This also works on Android with the Chrome Beta Browser, using the <i>Add to Homescreen</i> option 
 +                        in the browser's menu.
 +                    </p>
 +                    <p class='comment'> 
 +                        If you want to work in fullscreen just this time tap the <i>Go Fullscreen</i> button.
 +                    </p>
 +                </t>
 +
 +                <t t-if='!widget.ismobile()'>
 +                    <p class="comment">
 +                        The best way to make the point of sale fullscreen on desktop
 +                        and laptops is to launch your browser in kiosk mode. Please
 +                        refer to your browser's documentation for the specific 
 +                        instructions.
 +                    </p>
 +                    <p class="comment">
 +                        If you want to work in fullscreen just this time, click the <i> Go Fullscreen</i> button.
 +                    </p>
 +                </t>
 +
 +                <div class="footer">
 +                    <div class="button fullscreen">
 +                        Go Fullscreen
 +                    </div>
 +                    <div class="button cancel">
 +                        Cancel
 +                    </div>
 +                </div>
 +            </div>
 +        </div>
 +    </t>
 +
 +    <t t-name="ErrorInvoiceTransferPopupWidget">
 +        <div class="modal-dialog">
 +            <div class="popup popup-invoice">
 +                <p class="message">The Order could not be sent to the server for invoicing. Invoices cannot be generated
 +                    in offline mode. Please check your internet connection and try again.</p>
 +                <div class="footer">
 +                    <div class="button">
 +                        Ok
 +                    </div>
 +                </div>
 +            </div>
 +        </div>
 +    </t>
 +
      <t t-name="ErrorPopupWidget">
          <div class="modal-dialog">
              <div class="popup popup-error">
          </div>
      </t>
  
 +    <t t-name="ErrorTracebackPopupWidget">
 +        <div class="modal-dialog">
 +            <div class="popup popup-error">
 +                <p class="message"><t t-esc=" widget.message || 'Error' " /></p>
 +                <p class="comment traceback"><t t-esc=" widget.comment || '' "/></p>
 +                <div class="footer">
 +                    <div class="button">
 +                        Ok
 +                    </div>
 +                </div>
 +            </div>
 +        </div>
 +    </t>
 +
      <t t-name="ErrorBarcodePopupWidget">
          <div class="modal-dialog">
              <div class="popup popup-barcode">
          </tr>
      </t>
  
 -    <t t-name="PaypadButtonWidget">
 -        <button class="paypad-button" t-att-cash-register-id="widget.cashregister.id">
 -            <t t-esc="widget.cashregister.journal.name"/>
 -        </button>
 -    </t>
 -
      <t t-name="OrderButtonWidget">
          <span class="order-button select-order">
              <t t-if='widget.selected'>
                  </tr>
                  <t t-foreach="order.getTaxDetails()" t-as="taxdetail">
                      <tr>
-                         <td><t t-esc="taxdetail.tax.name" /></td>
+                         <td><t t-esc="taxdetail.name" /></td>
                          <td class="pos-right-align">
                              <t t-esc="widget.format_currency(taxdetail.amount)" />
                          </td>
          </div>
      </t>
  
 -    <t t-name="ActionBarWidget">
 -        <div class="pos-actionbar">
 -            <ul class="pos-actionbar-button-list">
 -            </ul>
 -        </div>
 -    </t>
 -
 -    <t t-name="ActionButtonWidget">
 -        <li t-att-class=" 'button '+ (widget.rightalign  ? 'rightalign ' : '') + (widget.disabled ? 'disabled ' : '')">
 -            <div class='label'>
 -                <t t-esc="widget.label" />
 -            </div>
 -        </li>
 -    </t>
 -
 -    <t t-name="ActionButtonWidgetWithIcon">
 -        <li t-att-class=" 'button '+ (widget.rightalign  ? 'rightalign ' : '') + (widget.disabled ? 'disabled ' : '')">
 -            <div class='icon'>
 -                <img t-att-src="widget.icon" />
 -                <div class='iconlabel'><t t-esc="widget.label" /></div>
 -            </div>
 -        </li>
 -    </t>
 -
      <!-- Onscreen Keyboard : 
           http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ -->
      <t t-name="OnscreenKeyboardFull">
@@@ -584,7 -584,13 +584,13 @@@ class product_template(osv.osv)
          res = {}
          product_uom_obj = self.pool.get('product.uom')
          for product in products:
-             res[product.id] = product[ptype] or 0.0
+             # standard_price field can only be seen by users in base.group_user
+             # Thus, in order to compute the sale price from the cost price for users not in this group
+             # We fetch the standard price as the superuser
+             if ptype != 'standard_price':
+                 res[product.id] = product[ptype] or 0.0
+             else:
+                 res[product.id] = product.sudo()[ptype]
              if ptype == 'list_price':
                  res[product.id] += product._name == "product.product" and product.price_extra or 0.0
              if 'uom' in context:
          ''' 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)
@@@ -990,6 -1002,10 +996,10 @@@ class product_product(osv.osv)
              return (d['id'], name)
  
          partner_id = context.get('partner_id', False)
+         if partner_id:
+             partner_ids = [partner_id, self.pool['res.partner'].browse(cr, user, partner_id, context=context).commercial_partner_id.id]
+         else:
+             partner_ids = []
  
          # all user don't have access to seller and partner
          # check access and use superuser
              variant = ", ".join([v.name for v in product.attribute_value_ids])
              name = variant and "%s (%s)" % (product.name, variant) or product.name
              sellers = []
-             if partner_id:
-                 sellers = filter(lambda x: x.name.id == partner_id, product.seller_ids)
+             if partner_ids:
+                 sellers = filter(lambda x: x.name.id in partner_ids, product.seller_ids)
              if sellers:
                  for s in sellers:
                      seller_variant = s.product_name and "%s (%s)" % (s.product_name, variant) or False
@@@ -11,7 -11,7 +11,7 @@@
              <field name="arch" type="xml">
                  <search string="Product">
                      <field name="name" string="Product"/>
 -                    <filter string="Services" icon="terp-accessories-archiver" domain="[('type','=','service')]"/>
 +                    <filter string="Services" name="services" domain="[('type','=','service')]"/>
                      <filter string="Consumable" name="consumable" icon="terp-accessories-archiver" domain="[('type','=','consu')]" help="Consumable products"/>
                      <separator/>
                      <filter string="Can be Sold" name="filter_to_sell" icon="terp-accessories-archiver-minus" domain="[('sale_ok','=',1)]"/>
@@@ -78,8 -78,6 +78,6 @@@
                                      </group>
                                      <group>
                                          <field name="active"/>
-                                         <field name="ean13" attrs="{'invisible': [('is_product_variant', '=', False), ('product_variant_count', '!=', 1)]}"/>
-                                         <field name="default_code" attrs="{'invisible': [('is_product_variant', '=', False), ('product_variant_count', '!=', 1)]}"/>
                                      </group>
                                  </group>
                                  <group colspan="4">
                  <xpath expr="//form" position="attributes">
                      <attribute name="name">Product Template</attribute>
                  </xpath>
+                 <field name="active" position="after">
+                     <field name="ean13" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
+                     <field name="default_code" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
+                 </field>
                  <xpath expr="//page[@string='Sales']" position="after">
                      <page name="variants" string="Variants">
                          <div class="oe_right">
  
          <!-- 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>
                  <form position="attributes">
                      <attribute name="string">Product Variant</attribute>
                  </form>
+                 <field name="active" position="after">
+                     <field name="ean13"/>
+                     <field name="default_code"/>
+                 </field>
                  <field name="list_price" position="attributes">
                     <attribute name="name">lst_price</attribute>
                  </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>
          </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>
  #
  ##############################################################################
  
 +import calendar
  from datetime import datetime, date
 +from dateutil import relativedelta
  from lxml import etree
 +import json
  import time
  
  from openerp import SUPERUSER_ID
@@@ -68,7 -65,6 +68,7 @@@ class project(osv.osv)
      _inherits = {'account.analytic.account': "analytic_account_id",
                   "mail.alias": "alias_id"}
      _inherit = ['mail.thread', 'ir.needaction_mixin']
 +    _period_number = 5
  
      def _auto_init(self, cr, context=None):
          """ Installation hook: aliases, project.project """
      def unlink(self, cr, uid, ids, context=None):
          alias_ids = []
          mail_alias = self.pool.get('mail.alias')
 +        analytic_account_to_delete = set()
          for proj in self.browse(cr, uid, ids, context=context):
              if proj.tasks:
                  raise osv.except_osv(_('Invalid Action!'),
                                       _('You cannot delete a project containing tasks. You can either delete all the project\'s tasks and then delete the project or simply deactivate the project.'))
              elif proj.alias_id:
                  alias_ids.append(proj.alias_id.id)
 +            if proj.analytic_account_id and not proj.analytic_account_id.line_ids:
 +                analytic_account_to_delete.add(proj.analytic_account_id.id)
          res = super(project, self).unlink(cr, uid, ids, context=context)
          mail_alias.unlink(cr, uid, alias_ids, context=context)
 +        self.pool['account.analytic.account'].unlink(cr, uid, list(analytic_account_to_delete), context=context)
          return res
  
      def _get_attached_docs(self, cr, uid, ids, field_name, arg, context):
              'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, res_id)
          }
  
 +    def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
 +        """ Generic method to generate data for bar chart values using SparklineBarWidget.
 +            This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
 +
 +            :param obj: the target model (i.e. crm_lead)
 +            :param domain: the domain applied to the read_group
 +            :param list read_fields: the list of fields to read in the read_group
 +            :param str value_field: the field used to compute the value of the bar slice
 +            :param str groupby_field: the fields used to group
 +
 +            :return list section_result: a list of dicts: [
 +                                                {   'value': (int) bar_column_value,
 +                                                    'tootip': (str) bar_column_tooltip,
 +                                                }
 +                                            ]
 +        """
 +        month_begin = date.today().replace(day=1)
 +        section_result = [{
 +                          'value': 0,
 +                          'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
 +                          } for i in range(self._period_number - 1, -1, -1)]
 +        group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
 +        pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
 +        for group in group_obj:
 +            group_begin_date = datetime.strptime(group['__domain'][0][2], pattern)
 +            month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
 +            section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)}
 +        return section_result
 +
 +    def _get_project_task_data(self, cr, uid, ids, field_name, arg, context=None):
 +        obj = self.pool['project.task']
 +        month_begin = date.today().replace(day=1)
 +        date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
 +        date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
 +        res = {}
 +        for id in ids:
 +            created_domain = [('project_id', '=', id), ('create_date', '>=', date_begin ), ('create_date', '<=', date_end ), ('stage_id.fold', '=', False)]
 +            res[id] = json.dumps(self.__get_bar_values(cr, uid, obj, created_domain, [ 'create_date'], 'create_date_count', 'create_date', context=context))
 +        return res
 +
      # Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
      _alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs)
      _visibility_selection = lambda self, *args, **kwargs: self._get_visibility_selection(*args, **kwargs)
                                     ('pending','Pending'),
                                     ('close','Closed')],
                                    'Status', required=True, copy=False),
 +        'monthly_tasks': fields.function(_get_project_task_data, type='char', readonly=True,
 +                                             string='Project Task By Month'),
          'doc_count': fields.function(
              _get_attached_docs, string="Number of documents attached", type='integer'
          )
          'privacy_visibility': 'employees',
      }
  
 +    def message_get_suggested_recipients(self, cr, uid, ids, context=None):
 +        recipients = super(project, self).message_get_suggested_recipients(cr, uid, ids, context=context)
 +        for data in self.browse(cr, uid, ids, context=context):
 +            if data.partner_id:
 +                reason = _('Customer Email') if data.partner_id.email else _('Customer')
 +                self._message_add_suggested_recipient(cr, uid, recipients, data, partner=data.partner_id, reason= '%s' % reason)
 +        return recipients
 +
      # TODO: Why not using a SQL contraints ?
      def _check_dates(self, cr, uid, ids, context=None):
          for leave in self.read(cr, uid, ids, ['date_start', 'date'], context=context):
@@@ -591,13 -533,9 +591,13 @@@ def Project()
          if vals.get('type', False) not in ('template', 'contract'):
              vals['type'] = 'contract'
  
 +        ir_values = self.pool.get('ir.values').get_default(cr, uid, 'project.config.settings', 'generate_project_alias')
 +        if ir_values:
 +            vals['alias_name'] = vals.get('alias_name') or vals.get('name')
          project_id = super(project, self).create(cr, uid, vals, context=create_context)
          project_rec = self.browse(cr, uid, project_id, context=context)
 -        self.pool.get('mail.alias').write(cr, uid, [project_rec.alias_id.id], {'alias_parent_thread_id': project_id, 'alias_defaults': {'project_id': project_id}}, context)
 +        values = {'alias_parent_thread_id': project_id, 'alias_defaults': {'project_id': project_id}}
 +        self.pool.get('mail.alias').write(cr, uid, [project_rec.alias_id.id], values, context=context)
          return project_id
  
      def write(self, cr, uid, ids, vals, context=None):
@@@ -783,13 -721,13 +783,13 @@@ class task(osv.osv)
      _columns = {
          'active': fields.function(_is_template, store=True, string='Not a Template Task', type='boolean', help="This field is computed automatically and have the same behavior than the boolean 'active' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked."),
          'name': fields.char('Task Summary', track_visibility='onchange', size=128, required=True, select=True),
 -        'description': fields.text('Description'),
 -        'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority', select=True),
 +        'description': fields.html('Description'),
 +        'priority': fields.selection([('0','Normal'), ('1','High')], 'Priority', select=True),
          'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
          'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange', select=True,
                          domain="[('project_ids', '=', project_id)]", copy=False),
          'categ_ids': fields.many2many('project.category', string='Tags'),
 -        'kanban_state': fields.selection([('normal', 'In Progress'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
 +        'kanban_state': fields.selection([('normal', 'In Progress'),('done', 'Ready for next stage'),('blocked', 'Blocked')], 'Kanban State',
                                           track_visibility='onchange',
                                           help="A task's kanban state indicates special situations affecting it:\n"
                                                " * Normal is the default situation\n"
                  'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
                  'project.task.work': (_get_task, ['hours'], 10),
              }),
 -        'reviewer_id': fields.many2one('res.users', 'Reviewer', select=True, track_visibility='onchange'),
          'user_id': fields.many2one('res.users', 'Assigned to', select=True, track_visibility='onchange'),
          'delegated_user_id': fields.related('child_ids', 'user_id', type='many2one', relation='res.users', string='Delegated To'),
          'partner_id': fields.many2one('res.partner', 'Customer'),
          'progress': 0,
          'sequence': 10,
          'active': True,
          'user_id': lambda obj, cr, uid, ctx=None: uid,
          'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=ctx),
          'partner_id': lambda self, cr, uid, ctx=None: self._get_default_partner(cr, uid, context=ctx),
 +        'date_start': fields.datetime.now,
      }
      _order = "priority desc, sequence, date_start, name, id"
  
          if vals.get('project_id') and not context.get('default_project_id'):
              context['default_project_id'] = vals.get('project_id')
          # user_id change: update date_start
-         if vals.get('user_id') and not vals.get('start_date'):
+         if vals.get('user_id') and not vals.get('date_start'):
              vals['date_start'] = fields.datetime.now()
  
          # context: no_log, because subtype already handle this
      # Mail gateway
      # ---------------------------------------------------
  
 -    def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=None, context=None):
 -        if auto_follow_fields is None:
 -            auto_follow_fields = ['user_id', 'reviewer_id']
 -        return super(task, self)._message_get_auto_subscribe_fields(cr, uid, updated_fields, auto_follow_fields, context=context)
 -
      def message_get_reply_to(self, cr, uid, ids, context=None):
          """ Override to get the reply_to of the parent project. """
          tasks = self.browse(cr, SUPERUSER_ID, ids, context=context)
              'planned_hours': 0.0,
          }
          defaults.update(custom_values)
 -        return super(task, self).message_new(cr, uid, msg, custom_values=defaults, context=context)
 +        res = super(task, self).message_new(cr, uid, msg, custom_values=defaults, context=context)
 +        email_list = tools.email_split(msg.get('to', '') + ',' + msg.get('cc', ''))
 +        new_task = self.browse(cr, uid, res, context=context)
 +        if new_task.project_id and new_task.project_id.alias_name:  # check left-part is not already an alias
 +            email_list = filter(lambda x: x.split('@')[0] != new_task.project_id.alias_name, email_list)
 +        partner_ids = filter(lambda x: x, self._find_partner_from_emails(cr, uid, None, email_list, context=context, check_followers=False))
 +        self.message_subscribe(cr, uid, [res], partner_ids, context=context)
 +        return res
  
      def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
          """ Override to update the task according to the email. """
@@@ -1284,12 -1221,12 +1284,12 @@@ class account_analytic_account(osv.osv)
              self.project_create(cr, uid, account.id, vals_for_project, context=context)
          return super(account_analytic_account, self).write(cr, uid, ids, vals, context=context)
  
 -    def unlink(self, cr, uid, ids, *args, **kwargs):
 -        project_obj = self.pool.get('project.project')
 -        analytic_ids = project_obj.search(cr, uid, [('analytic_account_id','in',ids)])
 -        if analytic_ids:
 -            raise osv.except_osv(_('Warning!'), _('Please delete the project linked with this account first.'))
 -        return super(account_analytic_account, self).unlink(cr, uid, ids, *args, **kwargs)
 +    def unlink(self, cr, uid, ids, context=None):
 +        proj_ids = self.pool['project.project'].search(cr, uid, [('analytic_account_id', 'in', ids)])
 +        has_tasks = self.pool['project.task'].search(cr, uid, [('project_id', 'in', proj_ids)], count=True, context=context)
 +        if has_tasks:
 +            raise osv.except_osv(_('Warning!'), _('Please remove existing tasks in the project linked to the accounts you want to delete.'))
 +        return super(account_analytic_account, self).unlink(cr, uid, ids, context=context)
  
      def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
          if args is None:
@@@ -253,7 -253,7 +253,7 @@@ class purchase_order(osv.osv)
                                      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."
@@@ -1353,6 -1353,7 +1353,7 @@@ class procurement_order(osv.osv)
                          'location_id': procurement.location_id.id,
                          'picking_type_id': procurement.rule_id.picking_type_id.id,
                          'pricelist_id': partner.property_product_pricelist_purchase.id,
+                         'currency_id': partner.property_product_pricelist_purchase and partner.property_product_pricelist_purchase.currency_id.id or procurement.company_id.currency_id.id,
                          'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                          'company_id': procurement.company_id.id,
                          'fiscal_position': partner.property_account_position and partner.property_account_position.id or False,
@@@ -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,product_uom,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)"/>
                                      <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,price_unit,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,price_unit,parent.state,context)"/>
                                      <field name="price_unit"/>
@@@ -28,6 -28,11 +28,6 @@@ function openerp_picking_widgets(instan
              var self = this;
              this.rows = [];
              this.search_filter = "";
 -            jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
 -                return function( elem ) {
 -                    return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
 -                };
 -            });
          },
          get_header: function(){
              return this.getParent().get_header();
                      .call('read',[[parseInt(picking_id)], [], new instance.web.CompoundContext()])
                      .then(function(picking){
                          self.picking = picking[0];
+                         self.picking_type_id = picking[0].picking_type_id[0];
                          return load_picking_list(self.picking.picking_type_id[0]);
                      });
              }else{
          scan: function(ean){ //scans a barcode, sends it to the server, then reload the ui
              var self = this;
              var product_visible_ids = this.picking_editor.get_visible_ids();
-             new instance.web.Model('stock.picking')
+             return new instance.web.Model('stock.picking')
                  .call('process_barcode_from_ui', [self.picking.id, ean, product_visible_ids])
                  .then(function(result){
                      if (result.filter_loc !== false){
          },
          scan_product_id: function(product_id,increment,op_id){ //performs the same operation as a scan, but with product id instead
              var self = this;
-             new instance.web.Model('stock.picking')
+             return new instance.web.Model('stock.picking')
                  .call('process_product_id_from_ui', [self.picking.id, product_id, op_id, increment])
                  .then(function(result){
                      return self.refresh_ui(self.picking.id);
              var self = this;
              var pack_op_ids = self.picking_editor.get_current_op_selection(false);
              if (pack_op_ids.length !== 0){
-                 new instance.web.Model('stock.picking')
+                 return new instance.web.Model('stock.picking')
                      .call('action_pack',[[[self.picking.id]], pack_op_ids])
                      .then(function(){
                          instance.session.user_context.current_package_id = false;
              var self = this;
              var pack_op_ids = self.picking_editor.get_current_op_selection(true);
              if (pack_op_ids.length !== 0){
-                 new instance.web.Model('stock.pack.operation')
+                 return new instance.web.Model('stock.pack.operation')
                      .call('action_drop_down', [pack_op_ids])
                      .then(function(){
                              return self.refresh_ui(self.picking.id).then(function(){
          },
          done: function(){
              var self = this;
-             new instance.web.Model('stock.picking')
+             return new instance.web.Model('stock.picking')
                  .call('action_done_from_ui',[self.picking.id, {'default_picking_type_id': self.picking_type_id}])
                  .then(function(new_picking_ids){
                      if (new_picking_ids){
          },
          create_lot: function(op_id, lot_name){
              var self = this;
-             new instance.web.Model('stock.pack.operation')
+             return new instance.web.Model('stock.pack.operation')
                  .call('create_and_assign_lot',[parseInt(op_id), lot_name])
                  .then(function(){
                      return self.refresh_ui(self.picking.id);
              if (is_src_dst){
                  vals = {'location_id': loc_id};
              }
-             new instance.web.Model('stock.pack.operation')
+             return new instance.web.Model('stock.pack.operation')
                  .call('write',[op_id, vals])
                  .then(function(){
                      return self.refresh_ui(self.picking.id);
          },
          print_package: function(package_id){
              var self = this;
-             new instance.web.Model('stock.quant.package')
+             return new instance.web.Model('stock.quant.package')
                  .call('action_print',[[package_id]])
                  .then(function(action){
                      return self.do_action(action);
          },
          print_picking: function(){
              var self = this;
-             new instance.web.Model('stock.picking.type').call('read', [[self.picking_type_id], ['code'], new instance.web.CompoundContext()])
+             return new instance.web.Model('stock.picking.type').call('read', [[self.picking_type_id], ['code'], new instance.web.CompoundContext()])
                  .then(function(pick_type){
-                     new instance.web.Model('stock.picking').call('do_print_picking',[[self.picking.id]])
+                     return new instance.web.Model('stock.picking').call('do_print_picking',[[self.picking.id]])
                             .then(function(action){
                                  return self.do_action(action);
                             });
          },
          delete_package_op: function(pack_id){
              var self = this;
-             new instance.web.Model('stock.pack.operation').call('search', [[['result_package_id', '=', pack_id]]])
+             return new instance.web.Model('stock.pack.operation').call('search', [[['result_package_id', '=', pack_id]]])
                  .then(function(op_ids) {
-                     new instance.web.Model('stock.pack.operation').call('write', [op_ids, {'result_package_id':false}])
+                     return new instance.web.Model('stock.pack.operation').call('write', [op_ids, {'result_package_id':false}])
                          .then(function() {
                              return self.refresh_ui(self.picking.id);
                          });
          set_operation_quantity: function(quantity, op_id){
              var self = this;
              if(quantity >= 0){
-                 new instance.web.Model('stock.pack.operation')
+                 return new instance.web.Model('stock.pack.operation')
                      .call('write',[[op_id],{'qty_done': quantity }])
                      .then(function(){
                          self.refresh_ui(self.picking.id);
          },
          set_package_pack: function(package_id, pack){
              var self = this;
-                 new instance.web.Model('stock.quant.package')
+                 return new instance.web.Model('stock.quant.package')
                      .call('write',[[package_id],{'ul_id': pack }]);
              return;
          },
          reload_pack_operation: function(){
              var self = this;
-             new instance.web.Model('stock.picking')
+             return new instance.web.Model('stock.picking')
                  .call('do_prepare_partial',[[self.picking.id]])
                  .then(function(){
                      self.refresh_ui(self.picking.id);
diff --combined addons/stock/stock.py
@@@ -1081,7 -1081,7 +1081,7 @@@ class stock_picking(osv.osv)
  
      @api.cr_uid_ids_context
      def open_barcode_interface(self, cr, uid, picking_ids, context=None):
 -        final_url="/barcode/web/#action=stock.ui&picking_id="+str(picking_ids[0])
 +        final_url="/stock/barcode/#action=stock.ui&picking_id="+str(picking_ids[0])
          return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
  
      @api.cr_uid_ids_context
              if picking.pack_operation_ids:
                  self.recompute_remaining_qty(cr, uid, picking, context=context)
  
+     def _prepare_values_extra_move(self, cr, uid, op, product, remaining_qty, context=None):
+         """
+         Creates an extra move when there is no corresponding original move to be copied
+         """
+         picking = op.picking_id
+         res = {
+             'picking_id': picking.id,
+             'location_id': picking.location_id.id,
+             'location_dest_id': picking.location_dest_id.id,
+             'product_id': product.id,
+             'product_uom': product.uom_id.id,
+             'product_uom_qty': remaining_qty,
+             'name': _('Extra Move: ') + op.product_id.name,
+             'state': 'draft',
+             }
+         return res
      def _create_extra_moves(self, cr, uid, picking, context=None):
          '''This function creates move lines on a picking, at the time of do_transfer, based on
          unexpected product transfers (or exceeding quantities) found in the pack operations.
              for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
                  if remaining_qty > 0:
                      product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
-                     vals = {
-                         'picking_id': picking.id,
-                         'location_id': picking.location_id.id,
-                         'location_dest_id': picking.location_dest_id.id,
-                         'product_id': product_id,
-                         'product_uom': product.uom_id.id,
-                         'product_uom_qty': remaining_qty,
-                         'name': _('Extra Move: ') + product.name,
-                         'state': 'draft',
-                     }
+                     vals = self._prepare_values_extra_move(cr, uid, op, product, remaining_qty, context=context)
                      moves.append(move_obj.create(cr, uid, vals, context=context))
          if moves:
              move_obj.action_confirm(cr, uid, moves, context=context)
@@@ -2724,14 -2732,6 +2732,14 @@@ class stock_inventory_line(osv.osv)
          'product_qty': 1,
      }
  
 +    def create(self, cr, uid, values, context=None):
 +        if context is None:
 +            context = {}
 +        product_obj = self.pool.get('product.product')
 +        if 'product_id' in values and not 'product_uom_id' in values:
 +            values['product_uom_id'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
 +        return super(stock_inventory_line, self).create(cr, uid, values, context=context)
 +
      def _resolve_inventory_line(self, cr, uid, inventory_line, context=None):
          stock_move_obj = self.pool.get('stock.move')
          diff = inventory_line.theoretical_qty - inventory_line.product_qty
@@@ -4116,7 -4116,7 +4124,7 @@@ class stock_picking_type(osv.osv)
      _order = 'sequence'
  
      def open_barcode_interface(self, cr, uid, ids, context=None):
 -        final_url = "/barcode/web/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
 +        final_url = "/stock/barcode/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
          return {'type': 'ir.actions.act_url', 'url': final_url, 'target': 'self'}
  
      def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
@@@ -18,7 -18,7 +18,7 @@@
                              <label for="name" class="oe_edit_only"/>
                              <h1>
                                  <field name="name" class="oe_inline"/>
-                               </h1>
+                             </h1>
                          </div>
                          <group>
                              <group>
@@@ -38,7 -38,7 +38,7 @@@
                                          <group>
                                              <group>
                                                  <field name="product_id"
-                                                     on_change="onchange_product_id(product_id)" domain="[('landed_cost_ok', '=', True)]" context="{'default_landed_cost_ok': True}"/>
+                                                     on_change="onchange_product_id(product_id)" domain="[('landed_cost_ok', '=', True)]" context="{'default_landed_cost_ok': True, 'form_view_ref':'stock_landed_costs.view_stock_landed_cost_type_form'}"/>
                                                  <field name="price_unit"/>
                                              </group>
                                              <group>
@@@ -50,7 -50,7 +50,7 @@@
                                          <field name="name"/>
                                      </form>
                                      <tree string="Cost Lines" editable="bottom">
-                                         <field name="product_id" on_change="onchange_product_id(product_id)" domain="[('landed_cost_ok', '=', True)]" context="{'default_landed_cost_ok': True}"/>
+                                         <field name="product_id" on_change="onchange_product_id(product_id)" domain="[('landed_cost_ok', '=', True)]" context="{'default_landed_cost_ok': True, 'form_view_ref':'stock_landed_costs.view_stock_landed_cost_type_form'}"/>
                                          <field name="name"/>
                                          <field name="account_id"/>
                                          <field name="split_method"/>
          <menuitem action="action_stock_landed_cost" name="Landed Costs" parent="menu_stock_landed_cost_main" id="menu_stock_landed_cost" sequence="1"/>
  
          <!-- Stock Landed Cost Type View -->
-       <record id="view_stock_landed_cost_type_form" model="ir.ui.view">
+         <record id="view_stock_landed_cost_type_form" model="ir.ui.view">
              <field name="name">stock.landed.cost.type.form</field>
              <field name="model">product.product</field>
-           <field name="arch" type="xml">
+             <field name="priority">25</field>
+             <field name="arch" type="xml">
                  <form string="Landed Costs">
-                   <sheet>
+                     <sheet>
                          <field name="image_medium" widget="image" class="oe_avatar oe_left"/>
                          <div class="oe_title">
                              <div class="oe_edit_only">
                                  <field name="name" class="oe_inline"/>
                              </h1>
                              <label for="categ_id" class="oe_edit_only"/>
-                             <h2><field name="categ_id"/></h2>
+                             <h2>
+                                 <field name="categ_id"/>
+                             </h2>
                              <div name="options" groups="base.group_user">
-                               <field name="landed_cost_ok" readonly="1"/>
-                               <label for="landed_cost_ok"/>
-                                 <field name="active" />
-                               <label for="active"/>
+                                 <field name="landed_cost_ok" readonly="1"/>
+                                 <label for="landed_cost_ok"/>
+                                 <field name="active"/>
+                                 <label for="active"/>
                              </div>
                          </div>
                          <notebook>
                              <page string="Information">
                                  <group>
                                      <group>
-                                       <field name="split_method"/>
+                                         <field name="split_method"/>
                                          <field name="standard_price"/>
                                          <field name="property_account_expense"/>
                                      </group>
                          <field name="message_ids" widget="mail_thread"/>
                      </div>
                  </form>
-           </field>
-       </record>
+             </field>
+         </record>
  
          <!-- Stock Landed Cost Type Tree View -->
          <record id="stock_landed_cost_tree_view" model="ir.ui.view">
              <field name="domain">[('landed_cost_ok','=',True)]</field>
              <field name="view_type">form</field>
              <field name="view_mode">tree,form</field>
-           <field name="context">{'default_landed_cost_ok': True}</field>
+             <field name="context">{'default_landed_cost_ok': True}</field>
              <field name="search_view_id" ref="product.product_search_form_view"/>
              <field name="help" type="html">
                <p class="oe_view_nocontent_create">
                </p>
              </field>
          </record>
-       
          <record id="stock_landed_cost_type_action1" model="ir.actions.act_window.view">
             <field eval="1" name="sequence"/>
             <field name="view_mode">tree</field>
             <field name="act_window_id" ref="stock_landed_cost_type_action"/>
          </record>
  
 -    <menuitem action="stock_landed_cost_type_action" name="Landed Cost Type" parent="stock.menu_stock_configuration" id="menu_stock_landed_cost_type" sequence="1"/>
 +      <menuitem action="stock_landed_cost_type_action" name="Landed Cost Type" parent="stock.menu_warehouse_config" id="menu_stock_landed_cost_type" sequence="5"/>
  
      </data>
  </openerp>
@@@ -8,6 -8,7 +8,7 @@@
    font-weight: normal;
    font-style: normal;
  }
  @font-face {
    font-family: "EntypoRegular";
    src: url("/web/static/src/font/entypo-webfont.eot") format("eot");
@@@ -18,6 -19,7 +19,7 @@@
    font-weight: normal;
    font-style: normal;
  }
  #oe_main_menu_navbar {
    min-height: 34px;
    z-index: 1001;
    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;
    text-decoration: none;
  }
  .openerp .oe_dropdown_arrow:after {
-   width: 0;
-   height: 0;
-   display: inline-block;
-   content: "&darr";
-   text-indent: -99999px;
    vertical-align: top;
    margin-top: 8px;
    margin-left: 3px;
  .openerp .oe_form .oe_form_field_boolean {
    width: auto;
  }
 -.openerp .oe_form .oe_datepicker_container {
 -  display: none;
 -}
  .openerp .oe_form .oe_datepicker_root {
    display: inline-block;
  }
  .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 {
      top: 0px;
    }
  }
  .kitten-mode-activated {
    background-size: cover;
    background-attachment: fixed;
@@@ -523,19 -523,19 +523,19 @@@ $sheet-padding: 16p
                  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
                  &:hover
                      text-decoration: none
      .oe_dropdown_arrow:after
-         width: 0
-         height: 0
-         display: inline-block
-         content: "&darr"
-         text-indent: -99999px
          vertical-align: top
          margin-top: 8px
          //margin-left set at 3px to avoid a strange overflow
              white-space: nowrap
          .oe_form_field_boolean
              width: auto
 -        .oe_datepicker_container
 -            display: none
          .oe_datepicker_root
              display: inline-block
          .oe_form_required
              .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
@@@ -258,24 -258,24 +258,24 @@@ openerp.ParentedMixin = 
                                current object is destroyed.
      */
      alive: function(promise, reject) {
-         var def = $.Deferred();
          var self = this;
-         promise.done(function() {
-             if (! self.isDestroyed()) {
-                 if (! reject)
+         return $.Deferred(function (def) {
+             promise.then(function () {
+                 if (!self.isDestroyed()) {
                      def.resolve.apply(def, arguments);
-                 else
-                     def.reject();
-             }
-         }).fail(function() {
-             if (! self.isDestroyed()) {
-                 if (! reject)
+                 }
+             }, function () {
+                 if (!self.isDestroyed()) {
                      def.reject.apply(def, arguments);
-                 else
+                 }
+             }).always(function () {
+                 if (reject) {
+                     // noop if def already resolved or rejected
                      def.reject();
-             }
-         });
-         return def.promise();
+                 }
+                 // otherwise leave promise in limbo
+             });
+         }).promise();
      },
      /**
       * Inform the object it should destroy itself, releasing any
@@@ -1517,13 -1517,6 +1517,13 @@@ openerp.time_to_str = function(obj) 
           + lpad(obj.getSeconds(),2);
  };
  
 +// jQuery custom plugins
 +jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
 +    return function( elem ) {
 +        return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
 +    };
 +});
 +
  openerp.declare = declare;
  
  return openerp;
@@@ -42,15 -42,15 +42,15 @@@ instance.web.form.FieldManagerMixin = 
      Gives new values for the fields contained in the view. The new values could not be setted
      right after the call to this method. Setting new values can trigger on_changes.
  
-     @param (dict) values A dictonnary with key = field name and value = new value.
-     @return (Deferred) Is resolved after all the values are setted.
+     @param {Object} values A dictonary with key = field name and value = new value.
+     @return {$.Deferred} Is resolved after all the values are setted.
      */
      set_values: function(values) {},
      /**
      Computes an OpenERP domain.
  
-     @param (list) expression An OpenERP domain.
-     @return (boolean) The computed value of the domain.
+     @param {Array} expression An OpenERP domain.
+     @return {boolean} The computed value of the domain.
      */
      compute_domain: function(expression) {},
      /**
@@@ -58,7 -58,7 +58,7 @@@
      the field are only supposed to use this context to evualuate their own, they should not
      extend it.
  
-     @return (CompoundContext) An OpenERP context.
+     @return {CompoundContext} An OpenERP context.
      */
      build_eval_context: function() {},
  };
@@@ -120,6 -120,7 +120,7 @@@ instance.web.FormView = instance.web.Vi
          this.is_initialized = $.Deferred();
          this.mutating_mutex = new $.Mutex();
          this.save_list = [];
+         this.render_value_defs = [];
          this.reload_mutex = new $.Mutex();
          this.__clicked_inside = false;
          this.__blur_timeout = null;
                  opacity: '1',
                  filter: 'alpha(opacity = 100)'
              });
 +            instance.web.bus.trigger('form_view_shown', self);
          });
      },
      do_hide: function () {
          } else if (mode === "create") {
              mode = "edit";
          }
+         this.render_value_defs = [];
          this.set({actual_mode: mode});
      },
      check_actual_mode: function(source, options) {
                  if (menu) {
                      menu.do_reload_needaction();
                  }
 +                instance.web.bus.trigger('form_view_saved', self);
              });
          }).always(function(){
              $(e.target).attr("disabled", false);
          });
      },
      on_button_cancel: function(event) {
+         var self = this;
          if (this.can_be_discarded()) {
              if (this.get('actual_mode') === 'create') {
                  this.trigger('history_back');
              } else {
                  this.to_view_mode();
-                 this.trigger('load_record', this.datarecord);
+                 $.when.apply(null, this.render_value_defs).then(function(){
+                     self.trigger('load_record', self.datarecord);
+                 });
              }
          }
          this.trigger('on_button_cancel');
@@@ -2241,7 -2244,10 +2246,10 @@@ instance.web.form.ReinitializeWidgetMix
  instance.web.form.ReinitializeFieldMixin =  _.extend({}, instance.web.form.ReinitializeWidgetMixin, {
      reinitialize: function() {
          instance.web.form.ReinitializeWidgetMixin.reinitialize.call(this);
-         this.render_value();
+         var res = this.render_value();
+         if (this.view && this.view.render_value_defs){
+             this.view.render_value_defs.push(res);
+         }
      },
  });
  
@@@ -2608,10 -2614,10 +2616,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: new moment({ y: 1900 }),
 +            endDate: new 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.convert_to_moment_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.convert_to_moment_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 = new 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"
  });
  
@@@ -3151,9 -3198,9 +3159,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();
@@@ -4499,11 -4546,9 +4507,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);
      }
@@@ -4710,9 -4755,8 +4718,8 @@@ instance.web.form.FieldMany2ManyTags = 
              self.render_tag(data);
          }
          if (! values || values.length > 0) {
-             this._display_orderer.add(self.get_render_data(values)).done(handle_names);
-         }
-         else{
+             return this._display_orderer.add(self.get_render_data(values)).done(handle_names);
+         } else {
              handle_names([]);
          }
      },
@@@ -349,8 -349,6 +349,8 @@@ instance.web.ActionManager = instance.w
              });
          }
  
 +        instance.web.bus.trigger('action', action);
 +
          // Ensure context & domain are evaluated and can be manipulated/used
          var ncontext = new instance.web.CompoundContext(options.additional_context, action.context || {});
          action.context = instance.web.pyeval.eval('context', ncontext);
          }
          var widget = executor.widget();
          if (executor.action.target === 'new') {
-             var pre_dialog = this.dialog;
+             var pre_dialog = (this.dialog && !this.dialog.isDestroyed()) ? this.dialog : null;
              if (pre_dialog){
                  // prevent previous dialog to consider itself closed,
                  // right now, as we're opening a new one (prevents
@@@ -756,7 -754,6 +756,7 @@@ instance.web.ViewManager =  instance.we
                  }
                  views.push(mode);
              }
 +            instance.web.bus.trigger('view_switch_mode', self, mode);
          });
          var item = _.extend({
              widget: this,
@@@ -1238,7 -1235,7 +1238,7 @@@ instance.web.Sidebar = instance.web.Wid
      add_items: function(section_code, items) {
          var self = this;
          if (items) {
 -            this.items[section_code].push.apply(this.items[section_code],items);
 +            this.items[section_code].unshift.apply(this.items[section_code],items);
              this.redraw();
          }
      },
@@@ -1513,7 -1510,6 +1513,7 @@@ instance.web.View = instance.web.Widget
      },
      do_show: function () {
          this.$el.show();
 +        instance.web.bus.trigger('view_shown', this);
      },
      do_hide: function () {
          this.$el.hide();
      do_switch_view: function() {
          this.trigger.apply(this, ['switch_mode'].concat(_.toArray(arguments)));
      },
-     /**
-      * Cancels the switch to the current view, switches to the previous one
-      *
-      * @param {Object} [options]
-      * @param {Boolean} [options.created=false] resource was created
-      * @param {String} [options.default=null] view to switch to if no previous view
-      */
-     do_search: function(view) {
+     do_search: function(domain, context, group_by) {
      },
      on_sidebar_export: function() {
          new instance.web.DataExport(this, this.dataset).open();
@@@ -49,6 -49,7 +49,6 @@@
  <t t-name="CrashManager.warning">
      <table cellspacing="0" cellpadding="0" border="0" class="oe_dialog_warning">
      <tr>
 -        <td class="oe_dialog_icon"><img t-att-src='_s + "/web/static/src/img/warning.png"'/></td>
          <td>
              <p>
                  <t t-js="d">
                          <td><label for="backup_pwd">Master Password:</label></td>
                          <td><input type="password" name="backup_pwd" class="required" /></td>
                      </tr>
 +                    <tr>
 +                        <td><label>Format:</label></td>
 +                        <td>
 +                            <input type="radio" name="format" checked="checked" value="zip" />
 +                            <label for="format" title="Archive containing a dump of your database and your whole filestore">Zip</label>
 +                            <input type="radio" name="format" value="binary" />
 +                            <label for="format" title="Binary dump of your database (PostgreSQL dump)">Binary</label>
 +                        </td>
 +                    </tr>
                  </table>
              </form>
              <form id="db_restore" name="restore_db_form" style="display: none; ">
  </t>
  <t t-name="ViewPager">
      <div class="oe_pager_value">
-         <t t-raw="__content__"/>
+         <t t-raw="0"/>
      </div>
      <ul class="oe_pager_group">
          <!--
                  <button class="oe_dropdown_toggle oe_dropdown_arrow" t-if="section.name != 'buttons'">
                      <t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/>
                      <t t-esc="section.label"/>
+                     <i class="fa fa-caret-down"/>
                  </button>
                  <t t-if="section.name == 'buttons'" t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
                      <button t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url"
  <t t-name="web.datepicker">
      <span>
          <t t-set="placeholder" t-value="widget.getParent().node and widget.getParent().node.attrs.placeholder"/>
 -        <input type="text" class="oe_datepicker_container" disabled="disabled" style="display: none;"/>
 -        <input type="text"
 -            t-att-name="widget.name"
 -            t-att-placeholder="placeholder"
 -            class="oe_datepicker_master"
 -        /><img class="oe_input_icon oe_datepicker_trigger" draggable="false"
 -               t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
 -               title="Select date" width="16" height="16" border="0"/>
 +        <div class="oe_datepicker_main input-group">
 +            <input type="text"
 +                t-att-name="widget.name"
 +                t-att-placeholder="placeholder"
 +                class="oe_datepicker_master"
 +            /><span><img class="oe_input_icon oe_datepicker_trigger datepickerbutton" draggable="false"
 +                   t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
 +                   title="Select date" width="16" height="16" border="0"/></span>
 +        </div>
      </span>
  </t>
  <t t-name="FieldDate">
              method="post" enctype="multipart/form-data" t-att-action="fileupload_action || '/web/binary/upload'">
              <input type="hidden" name="session_id" value="" t-if="widget.session.override_session"/>
              <input type="hidden" name="callback" t-att-value="fileupload_id"/>
-             <t t-raw="__content__"/>
+             <t t-raw="0"/>
              <input type="file" class="oe_form_binary_file" name="ufile" t-if="widget.widget!='image'"/>
              <input type="file" class="oe_form_binary_file" name="ufile" accept="image/*" t-if="widget.widget=='image'"/>
          </form>
      </div>
  </t>
  <t t-name="WidgetButton">
 +    <span t-if="widget.pre_text"> <t t-esc="widget.pre_text"/> </span>
      <button type="button" t-att-class="widget.is_stat_button ? 'oe_stat_button btn btn-default' : 'oe_button oe_form_button ' + (widget.node.attrs.class ? widget.node.attrs.class : '')"
          t-att-style="widget.node.attrs.style"
          t-att-tabindex="widget.node.attrs.tabindex"
          <span t-if="widget.string and !widget.is_stat_button"><t t-esc="widget.string"/></span>
          <div t-if="widget.string and widget.is_stat_button"><t t-esc="widget.string"/></div>
      </button>
 +    <span t-if="widget.post_text"> <t t-esc="widget.post_text"/> </span>
  </t>
  <t t-name="WidgetButton.tooltip" t-extend="WidgetLabel.tooltip">
      <t t-jquery="div.oe_tooltip_string" t-operation="replace">
              <button class="oe_button" id="add_field">Add</button>
              <button class="oe_button" id="remove_field">Remove</button>
              <button class="oe_button" id="remove_all_field">Remove All</button>
 +            <button class="oe_button" id="move_up">Move Up</button>
 +            <button class="oe_button" id="move_down">Move Down</button>
          </td>
          <td class="oe_export_fields_selector_right">
              <select name="fields_list" id="fields_list"
      <a href="javascript:void(0)"><t t-esc="text"/></a>
  </t>
  <t t-name="StatInfo">
 -    <strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t>
 +    <strong><t t-esc="value"/></strong><br/><t t-esc="text"/>
 +</t>
 +<button t-name="toggle_button" type="button"
 +    t-att-title="widget.string"
 +    style="box-shadow: none; white-space:nowrap;">
 +    <img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png"
 +    t-att-alt="widget.string"/>
 +</button>
  
  </templates>
@@@ -159,7 -159,7 +159,7 @@@ instance.web_kanban.KanbanView = instan
              case 'button':
              case 'a':
                  var type = node.attrs.type || '';
 -                if (_.indexOf('action,object,edit,open,delete'.split(','), type) !== -1) {
 +                if (_.indexOf('action,object,edit,open,delete,url'.split(','), type) !== -1) {
                      _.each(node.attrs, function(v, k) {
                          if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
                              node.attrs['data-' + k] = v;
                              }
                          }];
                      }
 -                    if (node.tag == 'a') {
 +                    if (node.tag == 'a' && node.attrs['data-type'] != "url") {
                          node.attrs.href = '#';
                      } else {
                          node.attrs.type = 'button';
                  if(!self.nb_records) {
                      self.no_result();
                  }
 +                self.trigger('kanban_groups_processed');
              });
          });
      },
                      if (_.isEmpty(records)) {
                          self.no_result();
                      }
 +                    self.trigger('kanban_dataset_processed');
                      def.resolve();
                  });
              }).done(null, function() {
@@@ -949,7 -947,6 +949,6 @@@ instance.web_kanban.KanbanRecord = inst
          this.$el.find('[title]').each(function(){
              $(this).tooltip({
                  delay: { show: 500, hide: 0},
-                 container: $(this),
                  title: function() {
                      var template = $(this).attr('tooltip');
                      if (!self.view.qweb.has_template(template)) {
       *  open on form/edit view : oe_kanban_global_click_edit
       */
      on_card_clicked: function(ev) {
 -        if(this.$el.find('.oe_kanban_global_click_edit').size()>0)
 +        if (this.$el.find('.oe_kanban_global_click').size() > 0 && this.$el.find('.oe_kanban_global_click').data('routing')) {
 +            instance.web.redirect(this.$el.find('.oe_kanban_global_click').data('routing') + "/" + this.id);
 +        }
 +        else if (this.$el.find('.oe_kanban_global_click_edit').size()>0)
              this.do_action_edit();
          else
              this.do_action_open();
          var button_attrs = $action.data();
          this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
      },
 +    do_action_url: function($action) {
 +        return instance.web.redirect($action.attr("href"));
 +     },
      do_reload: function() {
          var self = this;
          this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).done(function(records) {
@@@ -65,7 -65,7 +65,7 @@@ class ir_http(orm.AbstractModel)
                      import GeoIP
                      # updated database can be downloaded on MaxMind website
                      # http://dev.maxmind.com/geoip/legacy/install/city/
-                     geofile = config.get('geoip_database', '/usr/share/GeoIP/GeoLiteCity.dat')
+                     geofile = config.get('geoip_database')
                      if os.path.exists(geofile):
                          self.geo_ip_resolver = GeoIP.open(geofile, GeoIP.GEOIP_STANDARD)
                      else:
@@@ -88,7 -88,6 +88,7 @@@
  
              request.redirect = lambda url, code=302: werkzeug.utils.redirect(url_for(url), code)
              request.website = request.registry['website'].get_current_website(request.cr, request.uid, context=request.context)
 +            request.context['website_id'] = request.website.id
              langs = [lg[0] for lg in request.website.get_languages()]
              path = request.httprequest.path.split('/')
              if first_pass:
                          request.lang = request.website.default_lang_code
  
              request.context['lang'] = request.lang
 +            if not request.context.get('tz'):
 +                request.context['tz'] = request.session['geoip'].get('time_zone')
              if not func:
                  if path[1] in langs:
                      request.lang = request.context['lang'] = path.pop(1)
                  exception=exception,
                  traceback=traceback.format_exc(exception),
              )
 -            code = getattr(exception, 'code', code)
 +
 +            if isinstance(exception, werkzeug.exceptions.HTTPException):
 +                if exception.code is None:
 +                    # Hand-crafted HTTPException likely coming from abort(),
 +                    # usually for a redirect response -> return it directly
 +                    return exception
 +                else:
 +                    code = exception.code
  
              if isinstance(exception, openerp.exceptions.AccessError):
                  code = 403
                  if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
                      code = 403
  
 -            if isinstance(exception, werkzeug.exceptions.HTTPException) and code is None:
 -                # Hand-crafted HTTPException likely coming from abort(),
 -                # usually for a redirect response -> return it directly
 -                return exception
 -
              if code == 500:
                  logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
                  if 'qweb_exception' in values:
@@@ -45,14 -45,6 +45,14 @@@ class QWeb(orm.AbstractModel)
          'a': 'href',
      }
  
 +    re_remove_spaces = re.compile('\s+')
 +    PRESERVE_WHITESPACE = [
 +        'pre',
 +        'textarea',
 +        'script',
 +        'style',
 +    ]
 +
      def add_template(self, qcontext, name, node):
          # preprocessing for multilang static urls
          if request.website:
          super(QWeb, self).add_template(qcontext, name, node)
  
      def render_att_att(self, element, attribute_name, attribute_value, qwebcontext):
-         att, val = super(QWeb, self).render_att_att(element, attribute_name, attribute_value, qwebcontext)
+         URL_ATTRS = self.URL_ATTRS.get(element.tag)
+         is_website = request.website
+         for att, val in super(QWeb, self).render_att_att(element, attribute_name, attribute_value, qwebcontext):
+             if is_website and att == URL_ATTRS and isinstance(val, basestring):
+                 val = qwebcontext.get('url_for')(val)
+             yield (att, val)
  
-         if request.website and att == self.URL_ATTRS.get(element.tag) and isinstance(val, basestring):
-             val = qwebcontext.get('url_for')(val)
-         return att, val
  
      def get_converter_for(self, field_type):
          return self.pool.get(
              'website.qweb.field.' + field_type,
              self.pool['website.qweb.field'])
  
 +    def render_text(self, text, element, qwebcontext):
 +        compress = request and not request.debug and request.website and request.website.compress_html
 +        if compress and element.tag not in self.PRESERVE_WHITESPACE:
 +            text = self.re_remove_spaces.sub(' ', text.lstrip())
 +        return super(QWeb, self).render_text(text, element, qwebcontext)
 +
 +    def render_tail(self, tail, element, qwebcontext):
 +        compress = request and not request.debug and request.website and request.website.compress_html
 +        if compress and element.getparent().tag not in self.PRESERVE_WHITESPACE:
 +            # No need to recurse because those tags children are not html5 parser friendly
 +            tail = self.re_remove_spaces.sub(' ', tail.rstrip())
 +        return super(QWeb, self).render_tail(tail, element, qwebcontext)
 +
  class Field(orm.AbstractModel):
      _name = 'website.qweb.field'
      _inherit = 'ir.qweb.field'
@@@ -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;
  }
@@@ -104,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;
  }
  }
  
  .css_non_editable_mode_hidden {
-   display: none;
+   display: none !important;
  }
  
  /* ----- BOOTSTRAP FIX ----- */
  
  /* ----- 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%;
  }
@@@ -238,6 -238,10 +234,6 @@@ footer 
    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;
@@@ -356,114 -360,63 +352,171 @@@ ul.nav-stacked > li > a 
    padding: 4px 0;
  }
  
+ .o_image_floating {
+   width: 40%;
+   margin: 4px;
+ }
+ .o_image_floating div.o_container {
+   position: relative;
+ }
+ .o_image_floating div.o_container mark {
+   display: block;
+   position: absolute;
+   bottom: 0;
+   width: 100%;
+   background-color: rgba(86, 61, 124, 0.25);
+ }
+ .o_image_floating div.o_container mark a {
+   color: white;
+ }
+ .o_image_floating.o_hide_link div.o_container mark {
+   display: none;
+ }
+ .o_image_floating.o_margin_s {
+   margin-bottom: 4px;
+ }
+ .o_image_floating.o_margin_s.pull-right {
+   margin-left: 8px;
+ }
+ .o_image_floating.o_margin_s.pull-left {
+   margin-right: 8px;
+ }
+ .o_image_floating.o_margin_m {
+   margin-bottom: 8px;
+ }
+ .o_image_floating.o_margin_m.pull-right {
+   margin-left: 12px;
+ }
+ .o_image_floating.o_margin_m.pull-left {
+   margin-right: 12px;
+ }
+ .o_image_floating.o_margin_l {
+   margin-bottom: 12px;
+ }
+ .o_image_floating.o_margin_l.pull-right {
+   margin-left: 16px;
+ }
+ .o_image_floating.o_margin_l.pull-left {
+   margin-right: 16px;
+ }
+ .o_image_floating.o_margin_xl {
+   margin-bottom: 24px;
+ }
+ .o_image_floating.o_margin_xl.pull-right {
+   margin-left: 32px;
+ }
+ .o_image_floating.o_margin_xl.pull-left {
+   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 #aaaaaa;
++  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 #aaaaaa;
++  border: 1px solid #aaa;
 +  text-indent: initial;
 +  background-size: cover;
 +  opacity: 0.5;
-   background-color: black;
++  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;
@@@ -497,6 -450,62 +550,62 @@@ 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_black {
+   background-color: rgba(0, 0, 0, 0.9);
+   color: white;
+ }
+ .oe_green {
 -  background-color: #169c78;
++  background-color: #169C78;
+   color: white;
+ }
+ .oe_green .text-muted {
 -  color: #dddddd;
++  color: #ddd;
+ }
+ .oe_blue_light {
+   background-color: #41b6ab;
+   color: white;
+ }
+ .oe_blue_light .text-muted {
 -  color: #dddddd;
++  color: #ddd;
+ }
+ .oe_blue {
+   background-color: #34495e;
+   color: white;
+ }
+ .oe_orange {
+   background-color: #f05442;
+   color: white;
+ }
+ .oe_orange .text-muted {
 -  color: #dddddd;
++  color: #ddd;
+ }
+ .oe_purple {
+   background-color: #b163a3;
+   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 */
  .texttop {
    vertical-align: top;
@@@ -547,10 -556,10 +656,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;
  }
  
    margin: 40px auto;
  }
  
+ .oe_website_spinner {
+   width: 121px;
+ }
+ .oe_website_spinner input {
+   text-align: center;
+ }
  div.media_iframe_video {
    height: 0;
    margin: 0 auto;
@@@ -591,3 -607,63 +705,63 @@@ div.media_iframe_video .css_editable_mo
      transform: none !important;
    }
  }
+ /* Fix: backward compatibility saas-3 */
+ div.carousel .container > .carousel-caption {
+   position: absolute;
+   right: 50%;
+   left: 50%;
+   bottom: 20px;
+ }
+ div.carousel .container > .carousel-caption > div {
+   position: absolute;
+   text-align: left;
+   padding: 20px;
+   background: rgba(0, 0, 0, 0.4);
+   bottom: 20px;
+ }
+ div.carousel .container > .carousel-image {
+   top: 5%;
+   bottom: 5%;
+   position: absolute;
+   max-height: 90%;
+   margin: 0 auto;
+ }
+ div.carousel .item.text_image .container > .carousel-caption {
+   left: 10%;
+ }
+ div.carousel .item.text_image .container > .carousel-caption > div {
+   right: 50%;
+   margin-right: -20%;
+   max-width: 550px;
+ }
+ div.carousel .item.text_image .container > .carousel-image {
+   right: 10%;
+   left: 50%;
+ }
+ div.carousel .item.image_text .container > .carousel-caption {
+   right: 10%;
+ }
+ div.carousel .item.image_text .container > .carousel-caption > div {
+   left: 50%;
+   margin-left: -20%;
+   max-width: 550px;
+ }
+ div.carousel .item.image_text .container > .carousel-image {
+   right: 50%;
+   left: 10%;
+ }
+ div.carousel .item.text_only .container > .carousel-caption {
+   left: 10%;
+   right: 10%;
+   top: 10%;
+   bottom: auto;
+ }
+ div.carousel .item.text_only .container > .carousel-caption > div {
+   text-align: center;
+   background: transparent;
+   bottom: auto;
+   width: 100%;
+ }
+ div.carousel .item.text_only .container > .carousel-image {
+   display: none !important;
+ }
@@@ -192,6 -192,9 +192,6 @@@ foote
  .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
@@@ -285,102 -288,48 +285,144 @@@ ul.nav-stacked > li > 
  .hr
      padding: 4px 0
  
+ .o_image_floating
+     width: 40%
+     margin: 4px
+     div.o_container
+         position: relative
+         mark
+             display: block
+             position: absolute
+             bottom: 0
+             width: 100%
+             background-color: rgba(86,61,124,.25)
+             a
+                 color: white
+     &.o_hide_link div.o_container mark
+         display: none
+     &.o_margin_s
+         margin-bottom: 4px
+         &.pull-right
+             margin-left: 8px
+         &.pull-left
+             margin-right: 8px
+     &.o_margin_m
+         margin-bottom: 8px
+         &.pull-right
+             margin-left: 12px
+         &.pull-left
+             margin-right: 12px
+     &.o_margin_l
+         margin-bottom: 12px
+         &.pull-right
+             margin-left: 16px
+         &.pull-left
+             margin-right: 16px
+     &.o_margin_xl
+         margin-bottom: 24px
+         &.pull-right
+             margin-left: 32px
+         &.pull-left
+             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
+     background: rgba(200, 200, 200, 0.14)
+ .oe_black
+     background-color: rgba(0, 0, 0, 0.9)
+     color: white
+ .oe_green
+     background-color: #169C78
+     color: white
+     .text-muted
+         color: #ddd
+ .oe_blue_light
+     background-color: #41b6ab
+     color: white
+     .text-muted
+         color: #ddd
+ .oe_blue
+     background-color: #34495e
+     color: white
+ .oe_orange
+     background-color: #f05442
+     color: white
+     .text-muted
+         color: #ddd
+ .oe_purple
+     background-color: #b163a3
+     color: white
+     .text-muted
+         color: #ddd
+ .oe_red
+     background-color: #9C1b31
+     color: white
+     .text-muted
+         color: #ddd
  /* Misc */
  
  .texttop
@@@ -463,6 -456,11 +549,11 @@@ span[data-oe-type="monetary"
      overflow:hidden
      text-overflow:ellipsis
  
+ .oe_website_spinner
+     width: 121px
+     input
+         text-align: center
  div.media_iframe_video
      height: 0
      margin: 0 auto
          -ms-transform: none !important
          -o-transform: none !important
          transform: none !important
+ /* Fix: backward compatibility saas-3 */
+ div.carousel
+     .container
+         > .carousel-caption
+             position: absolute
+             right: 50%
+             left: 50%
+             bottom: 20px
+             > div
+                 position: absolute
+                 text-align: left
+                 padding: 20px
+                 background: rgba(0, 0, 0, 0.4)
+                 bottom: 20px
+         > .carousel-image
+             top: 5%
+             bottom: 5%
+             position: absolute
+             max-height: 90%
+             margin: 0 auto
+     .item.text_image
+         .container
+             > .carousel-caption
+                 left: 10%
+                 > div
+                     right: 50%
+                     margin-right: -20%
+                     max-width: 550px
+             > .carousel-image
+                 right: 10%
+                 left: 50%
+     .item.image_text
+         .container
+             > .carousel-caption
+                 right: 10%
+                 > div
+                     left: 50%
+                     margin-left: -20%
+                     max-width: 550px
+             > .carousel-image
+                 right: 50%
+                 left: 10%
+     .item.text_only
+         .container
+             > .carousel-caption
+                 left: 10%
+                 right: 10%
+                 top: 10%
+                 bottom: auto
+                 > div
+                     text-align: center
+                     background: transparent
+                     bottom: auto
+                     width: 100%
+             > .carousel-image
+                 display: none !important
              }
          });
  
 +        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 classes = (style && style.length ? "btn " : "") + style + " " + size;
  
              if ($e.hasClass('email-address') && $e.val().indexOf("@") !== -1) {
-                 def.resolve('mailto:' + val, false, label);
+                 def.resolve('mailto:' + val, false, label, classes);
              } else if ($e.val() && $e.val().length && $e.hasClass('page')) {
                  var data = $e.select2('data');
                  if (!data.create) {
-                     def.resolve(data.id, false, data.text);
+                     def.resolve(data.id, false, label || data.text, classes);
                  } else {
                      // Create the page, get the URL back
                      $.get(_.str.sprintf(
                              '/website/add/%s?noredirect=1', encodeURI(data.id)))
                          .then(function (response) {
-                             def.resolve(response, false, data.id);
+                             def.resolve(response, false, data.id, classes);
                          });
                  }
              } else {
              }
              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 () {
                      return false;
                  }
                  switch(m.type) {
-                 case 'attributes': // ignore .cke_focus being added or removed
-                     // ignore id modification
-                     if (m.attributeName === 'id') { return false; }
+                 case 'attributes':
+                     // ignore special attributes and .cke_focus class being added or removed
+                     var ignored_attrs = ['id', 'contenteditable', 'attributeeditable']
+                     if (_.contains(ignored_attrs, m.attributeName)) { return false; }
                      // if attribute is not a class, can't be .cke_focus change
                      if (m.attributeName !== 'class') { return true; }
  
@@@ -93,7 -93,7 +93,7 @@@
          options = _.extend({
              window_title: '',
              field_name: '',
-             default: '',
+             'default': '', // dict notation for IE<9
              init: function() {}
          }, options || {});
  
          var dialog = $(openerp.qweb.render('website.prompt', options)).appendTo("body");
          options.$dialog = dialog;
          var field = dialog.find(options.field_type).first();
-         field.val(options.default);
+         field.val(options['default']); // dict notation for IE<9
          field.fillWith = function (data) {
              if (field.is('select')) {
                  var select = field[0];
         ---------------------------------------------------- */ 
      var templates_def = $.Deferred().resolve();
      website.add_template_file = function(template) {
 +        var def = $.Deferred();
          templates_def = templates_def.then(function() {
 -            var def = $.Deferred();
              openerp.qweb.add_template(template, function(err) {
                  if (err) {
                      def.reject(err);
              });
              return def;
          });
 +        return def;
      };
  
      website.add_template_file('/website/static/src/xml/website.xml');
@@@ -1,6 -1,135 +1,6 @@@
  (function () {
      'use strict';
  
 -/*  Building block / Snippet Editor
 - 
 -    The building blocks appear in the edit bar website. These prebuilt html block
 -    allowing the designer to easily generate content on a page (drag and drop).
 -    Options allow snippets to add customizations part html code according to their
 -    selector (jQuery) and javascript object.
 -    
 -    How to create content?
 -
 -    Designers can add their own html block in the "snippets" (/website/views/snippets.xml).
 -    The block must be added in one of four menus (structure, content, feature or effect).
 -    Structure:
 -        <div>
 -            <div class="oe_snippet_thumbnail">
 -                <img class="oe_snippet_thumbnail_img" src="...image src..."/>
 -                <span class="oe_snippet_thumbnail_title">...Block Name...</span>
 -            </div>
 -            <div class="oe_snippet_body">
 -                ...
 -                <!-- 
 -                    The block with class 'oe_snippet_body' is inserted in the page.
 -                    This class is removed when the block is dropped.
 -                    The block can be made of any html tag and content. -->
 -            </div>
 -        </div>
 -
 -    How to create options?
 -
 -    Designers can add their own html block in the "snippet_options" (/website/views/snippets.xml).
 -    Structure:
 -
 -        <div data-snippet-option-id='...'           <!-- Required: javascript object id (but javascript
 -                                                        for this option object is not required) -->
 -            data-selector="..."                     <!-- Required: jQuery selector.
 -                                                        Apply options on all The part of html who 
 -                                                        match with this jQuery selector.
 -                                                        E.g.: If the selector is div, all div will be selected
 -                                                        and can be highlighted and assigned an editor.  -->
 -            data-selector-siblings="..."            <!-- Optional: jQuery selector.
 -                                                        The html part can be insert or move beside
 -                                                        the selected html block -->
 -            data-selector-children="..."            <!-- Optional: jQuery selector.
 -                                                        The html part can be insert or move inside
 -                                                        the selected html block -->
 -            data-selector-vertical-children='...'>  <!-- Optional: jQuery selector.
 -                                                        The html part can be insert or move inside
 -                                                        the selected html block. The drop zone is
 -                                                        displayed vertically -->
 -                ...
 -                <li><a href="#">...</a></li>        <!-- Optional: html li list.
 -                                                        List of menu items displayed in customize
 -                                                        menu. If the li tag have 'data-class', the
 -                                                        class is automaticcally added or removed to
 -                                                        the html content when the user select this item. -->
 -                ...
 -                <li class="dropdown-submenu"                <!-- Optional: html li list exemple. !-->
 -                    data-required="true">                   <!-- Optional: if only one item can be selected
 -                                                                and can't be unselect. !-->
 -                    <a tabindex="-1" href="#">...</a>       <!-- bootstrap dropdown button !-->
 -                    <ul class="dropdown-menu">
 -                        <li data-value="text_only"><a>...</a></li>      <!-- by default data-value is apply
 -                                                                            like a class to html block !-->
 -                    </ul>
 -                </li>
 -        </div>
 -
 -        How to create a javascript object for an options?
 -
 -        openerp.website.snippet.options["...option-id..."] = website.snippet.Option.extend({
 -            // start is called when the user click into a block or when the user drop a block 
 -            // into the page (just after the init method).
 -            // start is usually used to bind event.
 -            //
 -            // this.$target: block html inserted inside the page
 -            // this.$el: html li list of this options
 -            // this.$overlay: html editor overlay who content resize bar, customize menu...
 -            start: function () {},
 -
 -
 -            // onFocus is called when the user click inside the block inserted in page
 -            // and when the user drop on block into the page
 -            onFocus : function () {},
 -
 -
 -            // onBlur is called when the user click outside the block inserted in page, if
 -            // the block is focused
 -            onBlur : function () {},
 -
 -
 -            // on_clone is called when the snippet is duplicate
 -            // @variables: $clone is allready inserted is the page
 -            on_clone: function ($clone) {},
 -
 -
 -            // on_remove is called when the snippet is removed (dom is removing after this tigger)
 -            on_remove: function () {},
 -
 -
 -            // drop_and_build_snippet is called just after that a thumbnail is drag and dropped
 -            // into a drop zone. The content is already inserted in the page.
 -            drop_and_build_snippet: function () {},
 -
 -            // select is called when a user select an item in the li list of options
 -            // By default, if the li item have a data-value attribute, the data-vlue it's apply
 -            // like a class to the html block (this.$target)
 -            // @variables: next_previous = {$next, $prev}
 -            //      $next = next item selected or false
 -            //      $prev = previous item selected or false
 -            select: function (event, next_previous) {}
 -
 -            // preview is called when a user is on mouse over or mouse out of an item
 -            // variables: next_previous = {$next, $prev}
 -            //      $next = next item selected or false
 -            //      $prev = previous item selected or false
 -            preview: function (event, next_previous) {}
 -
 -            // clean_for_save
 -            // clean_for_save is called just before to save the vue
 -            // Sometime it's important to remove or add some datas (contentEditable, added 
 -            // classes to a running animation...)
 -            clean_for_save: function () {}
 -        });
 -
 -
 -    // 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped
 -    // 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated
 -
 -*/
 -
      var dummy = function () {};
  
      var website = openerp.website;
          }
      });
  
 +    $.extend($.expr[':'],{
 +        checkData: function(node,i,m){
 +            var dataName = m[3];
 +            while (node) {
 +                if (node.dataset && node.dataset[dataName]) {
 +                    return true;
 +                } else {
 +                    node = node.parentNode;
 +                }
 +            }
 +            return false;
 +        },
 +        hasData: function(node,i,m){
 +            return !!_.toArray(node.dataset).length;
 +        },
 +    });
 +
      if (!website.snippet) website.snippet = {};
 -    website.snippet.templateOptions = {};
 +    website.snippet.templateOptions = [];
      website.snippet.globalSelector = "";
      website.snippet.selector = [];
      website.snippet.BuildingBlock = openerp.Widget.extend({
                  subtree: true,
              });
          },
 -        dom_filter: function (dom, sibling) {
 -            if (typeof dom === "string") {
 -                var include = "[data-oe-model]";
 -                var sdom = dom.split(',');
 -                dom = "";
 -                _.each(sdom, function (val) {
 -                    val = val.replace(/^\s+|\s+$/g, '');
 -                    dom += include + " " + val + ", ";
 -                    if (!sibling) {
 -                        val = val.split(" ");
 -                        dom += val.shift() + include + val.join(" ") + ", ";
 -                    }
 -                });
 -                dom = dom.replace(/,\s*$/g, '');
 -                return $(dom);
 -            } else {
 -                return (!sibling && $(dom).is("[data-oe-model]")) || $(dom).parents("[data-oe-model]").length ? $(dom) : $("");
 -            }
 -        },
          start: function() {
              var self = this;
  
                  .prependTo(this.parent.$("#website-top-edit ul"))
                  .find("button");
  
 -            this.$button.click(function () {
 -                self.make_active(false);
 -                self.$el.toggleClass("hidden");
 -            });
 -            $("#wrapwrap").click(function () {
 +            this.$button.click(_.bind(this.show_blocks, this));
 +
 +            this.$snippet = $("#oe_snippets");
 +            this.$wrapwrap = $("#wrapwrap");
 +            this.$wrapwrap.click(function () {
                  self.$el.addClass("hidden");
              });
  
              this.fetch_snippet_templates();
 -
              this.bind_snippet_click_editor();
 -
              this.$el.addClass("hidden");
  
              $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
              this.getParent().on('change:height', this, function (editor) {
                  self.$el.css('top', editor.get('height'));
              });
 -            self.$el.css('top', this.parent.get('height'));
 +            this.$el.css('top', this.parent.get('height'));
 +        },
 +        show_blocks: function () {
 +            var self = this;
 +            this.make_active(false);
 +            this.$el.toggleClass("hidden");
 +            if (this.$el.hasClass("hidden")) {
 +                return;
 +            }
 +
 +            //this.enable_snippets( this.$snippet.find(".tab-pane.active") );
 +            var categories = this.$snippet.find(".tab-pane.active")
 +                .add(this.$snippet.find(".tab-pane:not(.active)"))
 +                .get().reverse();
 +            function enable() {
 +                self.enable_snippets( $(categories.pop()) );
 +                if (categories.length) {
 +                    setTimeout(enable,10);
 +                }
 +            }
 +            setTimeout(enable,0);
 +        },
 +        enable_snippets: function ($category) {
 +            var self = this;
 +            $category.find(".oe_snippet_body").each(function () {
 +                var $snippet = $(this);
 +
 +                if (!$snippet.data('selectors')) {
 +                    var selectors = [];
 +                    for (var k in website.snippet.templateOptions) {
 +                        var option = website.snippet.templateOptions[k];
 +                        if ($snippet.is(option.base_selector)) {
 +
 +                            var dropzone = [];
 +                            if (option['drop-near']) dropzone.push(option['drop-near']);
 +                            if (option['drop-in']) dropzone.push(option['drop-in']);
 +                            if (option['drop-in-vertical']) dropzone.push(option['drop-in-vertical']);
 +                            selectors = selectors.concat(dropzone);
 +                        }
 +                    }
 +                    $snippet.data('selectors', selectors.length ? selectors.join(":first, ") + ":first" : "");
 +                }
 +
 +                if ($snippet.data('selectors').length && self.$wrapwrap.find($snippet.data('selectors')).size()) {
 +                    $snippet.closest(".oe_snippet").removeClass("disable");
 +                } else {
 +                    $snippet.closest(".oe_snippet").addClass("disable");
 +                }
 +            });
 +            $('#oe_snippets .scroll a[data-toggle="tab"][href="#' + $category.attr("id") + '"]')
 +                .toggle(!!$category.find(".oe_snippet:not(.disable)").size());
          },
          _get_snippet_url: function () {
              return '/website/snippets';
          },
 +        _add_check_selector : function (selector, no_check) {
 +            var data = selector.split(",");
 +            var selectors = [];
 +            for (var k in data) {
 +                selectors.push(data[k].replace(/^\s+|\s+$/g, '') + (no_check ? "" : ":checkData(oeModel)"));
 +            }
 +            return selectors.join(", ");
 +        },
          fetch_snippet_templates: function () {
              var self = this;
  
                      var $html = $(html);
  
                      var selector = [];
 -                    var $styles = $html.find("[data-snippet-option-id]");
 +                    var $styles = $html.find("[data-js], [data-selector]");
                      $styles.each(function () {
                          var $style = $(this);
 -                        var style_id = $style.data('snippet-option-id');
 -                        website.snippet.templateOptions[style_id] = {
 -                            'snippet-option-id' : style_id,
 -                            'selector': $style.data('selector'),
 +                        var no_check = $style.data('no-check');
 +                        var option_id = $style.data('js');
 +                        var option = {
 +                            'option' : option_id,
 +                            'base_selector': $style.data('selector'),
 +                            'selector': self._add_check_selector($style.data('selector'), no_check),
                              '$el': $style,
 -                            'selector-siblings': $style.data('selector-siblings'),
 -                            'selector-children': $style.data('selector-children'),
 -                            'selector-vertical-children': $style.data('selector-vertical-children'),
 +                            'drop-near': $style.data('drop-near') && self._add_check_selector($style.data('drop-near'), no_check),
 +                            'drop-in': $style.data('drop-in') && self._add_check_selector($style.data('drop-in'), no_check),
                              'data': $style.data()
                          };
 -                        selector.push($style.data('selector'));
 +                        website.snippet.templateOptions.push(option);
 +                        selector.push(option.selector);
                      });
                      $styles.addClass("hidden");
                      website.snippet.globalSelector = selector.join(",");
  
 -                    self.$snippets = $html.find(".tab-content > div > div").addClass("oe_snippet");
 -                    self.$el.append($html);
 -
 +                    self.$snippets = $html.find(".tab-content > div > div")
 +                        .addClass("oe_snippet")
 +                        .each(function () {
 +                            if (!$('.oe_snippet_thumbnail', this).size()) {
 +                                var $div = $(
 +                                    '<div class="oe_snippet_thumbnail">'+
 +                                        '<div class="oe_snippet_thumbnail_img"/>'+
 +                                        '<span class="oe_snippet_thumbnail_title"></span>'+
 +                                    '</div>');
 +                                $div.find('span').text($(this).attr("name"));
 +                                $(this).prepend($div);
 +                            }
 +                            $("> *:not(.oe_snippet_thumbnail)", this).addClass('oe_snippet_body');
 +                        });
  
 -                    var snippets = 0;
 -                    self.$snippets.each(function () {
 -                        if (self.snippet_have_dropzone($(this)))
 -                            snippets++;
 -                    });
 -                    if (!snippets) self.$button.css("display", "none");
 +                    self.$el.append($html);
  
                      self.make_snippet_draggable(self.$snippets);
                  });
          hide: function () {
              this.$el.addClass("hidden");
          },
          bind_snippet_click_editor: function () {
              var self = this;
              var snipped_event_flag;
 -            $("#wrapwrap").on('click', function (event) {
 +            self.$wrapwrap.on('click', function (event) {
                  var srcElement = event.srcElement || (event.originalEvent && (event.originalEvent.originalTarget || event.originalEvent.target));
                  if (snipped_event_flag || !srcElement) {
                      return;
                      $target = $target.parents(website.snippet.globalSelector).first();
                  }
  
 -                if (!self.dom_filter($target).length) {
 -                    $target = false;
 -                }
                  if (self.$active_snipped_id && self.$active_snipped_id.is($target)) {
                      return;
                  }
          snippet_blur: function ($snippet) {
              if ($snippet) {
                  if ($snippet.data("snippet-editor")) {
 -                    $snippet.data("snippet-editor").onBlur();
 +                    $snippet.data("snippet-editor").on_blur();
                  }
              }
          },
          snippet_focus: function ($snippet) {
              if ($snippet) {
                  if ($snippet.data("snippet-editor")) {
 -                    $snippet.data("snippet-editor").onFocus();
 +                    $snippet.data("snippet-editor").on_focus();
                  }
              }
          },
              var self = this;
              var options = website.snippet.options;
              var template = website.snippet.templateOptions;
 -            for (var k in options) {
 -                if (template[k] && options[k].prototype.clean_for_save !== dummy) {
 -                    var $snippet = this.dom_filter(template[k].selector);
 -                    $snippet.each(function () {
 -                        new options[k](self, null, $(this), k).clean_for_save();
 +            for (var k in template) {
 +                var Option = options[template[k]['option']];
 +                if (Option && Option.prototype.clean_for_save !== dummy) {
 +                    self.$wrapwrap.find(template[k].selector).each(function () {
 +                        new Option(self, null, $(this), k).clean_for_save();
                      });
                  }
              }
 -            $("*[contentEditable], *[attributeEditable]")
 +            self.$wrapwrap.find("*[contentEditable], *[attributeEditable]")
                  .removeAttr('contentEditable')
                  .removeAttr('attributeEditable');
          },
                  this.create_overlay(this.$active_snipped_id);
                  this.snippet_focus($snippet);
              }
 -            $("#oe_snippets").trigger('snippet-activated', $snippet);
 +            this.$snippet.trigger('snippet-activated', $snippet);
              if ($snippet) {
                  $snippet.trigger('snippet-activated', $snippet);
              }
              this.cover_target($snippet.data('overlay'), $snippet);
          },
  
          // activate drag and drop for the snippets in the snippet toolbar
          make_snippet_draggable: function($snippets){
              var self = this;
 -            var $tumb = $snippets.find(".oe_snippet_thumbnail:first");
 +            var $tumb = $snippets.find(".oe_snippet_thumbnail_img:first");
              var left = $tumb.outerWidth()/2;
              var top = $tumb.outerHeight()/2;
              var $toInsert, dropped, $snippet, action, snipped_id;
                  start: function(){
                      self.hide();
                      dropped = false;
 -                    // snippet_selectors => to get selector-siblings, selector-children, selector-vertical-children
 +                    // snippet_selectors => to get drop-near, drop-in
                      $snippet = $(this);
 -                    $toInsert = $snippet.find('.oe_snippet_body').clone();
 -
 +                    var $base_body = $snippet.find('.oe_snippet_body');
                      var selector = [];
                      var selector_siblings = [];
                      var selector_children = [];
 -                    var selector_vertical_children = [];
 -                    for (var k in website.snippet.templateOptions) {
 -                        if ($toInsert.is(website.snippet.templateOptions[k].selector)) {
 -                            selector.push(website.snippet.templateOptions[k].selector);
 -                            if (website.snippet.templateOptions[k]['selector-siblings'])
 -                                selector_siblings.push(website.snippet.templateOptions[k]['selector-siblings']);
 -                            if (website.snippet.templateOptions[k]['selector-children'])
 -                                selector_children.push(website.snippet.templateOptions[k]['selector-children']);
 -                            if (website.snippet.templateOptions[k]['selector-vertical-children'])
 -                                selector_vertical_children.push(website.snippet.templateOptions[k]['selector-vertical-children']);
 +                    var vertical = false;
 +                    var temp = website.snippet.templateOptions;
 +                    for (var k in temp) {
 +                        if ($base_body.is(temp[k].base_selector)) {
 +                            selector.push(temp[k].base_selector);
 +                            if (temp[k]['drop-near'])
 +                                selector_siblings.push(temp[k]['drop-near']);
 +                            if (temp[k]['drop-in'])
 +                                selector_children.push(temp[k]['drop-in']);
                          }
                      }
  
 +                    $toInsert = $base_body.clone();
                      action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate';
  
                      if( action === 'insert'){
 -                        if (!selector_siblings.length && !selector_children.length && !selector_vertical_children.length) {
 -                            console.debug($snippet.data("snippet-id") + " have oe_snippet_body class and have not for insert action"+
 -                                "data-selector-siblings, data-selector-children or data-selector-vertical-children tag for mutate action");
 +                        if (!selector_siblings.length && !selector_children.length) {
 +                            console.debug($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
                              return;
                          }
                          self.activate_insertion_zones({
                              siblings: selector_siblings.join(","),
                              children: selector_children.join(","),
 -                            vertical_children: selector_vertical_children.join(","),
                          });
  
                      } else if( action === 'mutate' ){
                          if (!$snippet.data('selector')) {
 -                            console.debug($snippet.data("snippet-id") + " have not oe_snippet_body class and have not data-selector tag");
 +                            console.debug($snippet.data("option") + " have not oe_snippet_body class and have not data-selector tag");
                              return;
                          }
                          var $targets = self.activate_overlay_zones(selector_children.join(","));
                  stop: function(ev, ui){
                      $toInsert.removeClass('oe_snippet_body');
                      
 -                    if (action === 'insert' && ! dropped && $('.oe_drop_zone') && ui.position.top > 3) {
 -                        var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
 +                    if (action === 'insert' && ! dropped && self.$wrapwrap.find('.oe_drop_zone') && ui.position.top > 3) {
 +                        var el = self.$wrapwrap.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
                          if (el.length) {
                              el.after($toInsert);
                              dropped = true;
                          }
                      }
  
 -                    $('.oe_drop_zone').droppable('destroy').remove();
 +                    self.$wrapwrap.find('.oe_drop_zone').droppable('destroy').remove();
                      
                      if (dropped) {
                          var $target = false;
                          $target = $toInsert;
  
                          setTimeout(function () {
 -                            $("#oe_snippets").trigger('snippet-dropped', $target);
 +                            self.$snippet.trigger('snippet-dropped', $target);
  
                              website.snippet.start_animation(true, $target);
 -                            // drop_and_build_snippet
 -                            self.create_overlay($target);
 -                            if ($target.data("snippet-editor")) {
 -                                $target.data("snippet-editor").drop_and_build_snippet();
 -                            }
 -                            for (var k in website.snippet.templateOptions) {
 -                                $target.find(website.snippet.templateOptions[k].selector).each(function () {
 -                                    var $snippet = $(this);
 -                                    self.create_overlay($snippet);
 -                                    if ($snippet.data("snippet-editor")) {
 -                                        $snippet.data("snippet-editor").drop_and_build_snippet();
 -                                    }
 -                                });
 -                            }
  
                              // reset snippet for rte
                              $target.removeData("snippet-editor");
                                      $snippet.removeData("overlay");
                                  }
                              });
 +                            // end
 +
 +                            // drop_and_build_snippet
                              self.create_overlay($target);
 +                            if ($target.data("snippet-editor")) {
 +                                $target.data("snippet-editor").drop_and_build_snippet();
 +                            }
 +                            for (var k in website.snippet.templateOptions) {
 +                                $target.find(website.snippet.templateOptions[k].selector).each(function () {
 +                                    var $snippet = $(this);
 +                                    self.create_overlay($snippet);
 +                                    if ($snippet.data("snippet-editor")) {
 +                                        $snippet.data("snippet-editor").drop_and_build_snippet();
 +                                    }
 +                                });
 +                            }
                              // end
  
                              self.make_active($target);
          // return the original snippet in the editor bar from a snippet id (string)
          get_snippet_from_id: function(id){
              return $('.oe_snippet').filter(function(){
 -                    return $(this).data('snippet-id') === id;
 +                    return $(this).data('option') === id;
                  }).first();
          },
  
              var self = this;
              var child_selector = selector.children;
              var sibling_selector = selector.siblings;
 -            var vertical_child_selector   =  selector.vertical_children;
  
 -            var zone_template = "<div class='oe_drop_zone oe_insert'></div>";
 +            var zone_template = $("<div class='oe_drop_zone oe_insert'></div>");
  
              if(child_selector){
 -                self.dom_filter(child_selector).each(function (){
 -                    var $zone = $(this);
 -                    $zone.find('> *:not(.oe_drop_zone):visible').after(zone_template);
 -                    $zone.prepend(zone_template);
 -                });
 -            }
 -
 -            if(vertical_child_selector){
 -                self.dom_filter(vertical_child_selector).each(function (){
 +                self.$wrapwrap.find(child_selector).each(function (){
                      var $zone = $(this);
 -                    var $template = $(zone_template).addClass("oe_vertical");
 -                    var nb = 0;
 -                    var $lastinsert = false;
 -                    var left = 0;
 -                    var temp_left = 0;
 -                    $zone.find('> *:not(.oe_drop_zone):visible').each(function () {
 -                        var $col = $(this);
 -                        $template.css('height', ($col.outerHeight() + parseInt($col.css("margin-top")) + parseInt($col.css("margin-bottom")))+'px');
 -                        $lastinsert = $template.clone();
 -                        $(this).after($lastinsert);
 -
 -                        temp_left = $col.position().left;
 -                        if (left === temp_left) {
 -                            $col.prev(".oe_drop_zone.oe_vertical").remove();
 -                            $col.before($template.clone().css("clear", "left"));
 -                        }
 -                        else if (!nb) {
 -                            $col.before($template.clone());
 -                        }
 -                        left = temp_left;
 -                        nb ++;
 -                    });
 -                    if (!nb) {
 -                        $zone.prepend($template.css('height', $zone.outerHeight()+'px'));
 +                    var vertical;
 +                    var float = window.getComputedStyle(this).float;
 +                    if (float === "left" || float === "right") {
 +                        vertical = $zone.parent().outerHeight()+'px';
 +                    }
 +                    var $drop = zone_template.clone();
 +                    if (vertical) {
 +                        $drop.addClass("oe_vertical").css('height', vertical);
                      }
 +                    $zone.find('> *:not(.oe_drop_zone):visible').after($drop);
 +                    $zone.prepend($drop.clone());
                  });
              }
  
              if(sibling_selector){
 -                self.dom_filter(sibling_selector, true).each(function (){
 +                self.$wrapwrap.find(sibling_selector, true).each(function (){
                      var $zone = $(this);
 +                    var $drop, vertical;
 +                    var float = window.getComputedStyle(this).float;
 +                    if (float === "left" || float === "right") {
 +                        vertical = $zone.parent().outerHeight()+'px';
 +                    }
 +
                      if($zone.prev('.oe_drop_zone:visible').length === 0){
 -                        $zone.before(zone_template);
 +                        $drop = zone_template.clone();
 +                        if (vertical) {
 +                            $drop.addClass("oe_vertical").css('height', vertical);
 +                        }
 +                        $zone.before($drop);
                      }
                      if($zone.next('.oe_drop_zone:visible').length === 0){
 -                        $zone.after(zone_template);
 +                        $drop = zone_template.clone();
 +                        if (vertical) {
 +                            $drop.addClass("oe_vertical").css('height', vertical);
 +                        }
 +                        $zone.after($drop);
                      }
                  });
              }
                  // count += $zones.length;
                  // $zones.remove();
  
 -                $zones = $('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove();   // no recursive zones
 +                $zones = self.$wrapwrap.find('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove();   // no recursive zones
                  count += $zones.length;
                  $zones.remove();
              } while (count > 0);
  
 -            // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones.
 -            var $zones = $('.oe_drop_zone:not(.oe_vertical)');
 +            // Cleaning consecutive zone and up zones placed between floating or inline elements. We do not like these kind of zones.
 +            var $zones = self.$wrapwrap.find('.oe_drop_zone:not(.oe_vertical)');
              $zones.each(function (){
                  var zone = $(this);
                  var prev = zone.prev();
                  var next = zone.next();
 +                // remove consecutive zone
 +                if (!zone.hasClass('.oe_vertical') && (prev.is('.oe_drop_zone:not(.oe_vertical)') || next.is('.oe_drop_zone:not(.oe_vertical)'))) {
 +                    zone.remove();
 +                    return;
 +                }
                  var float_prev = prev.css('float')   || 'none';
                  var float_next = next.css('float')   || 'none';
                  var disp_prev  = prev.css('display') ||  null;
          // generate drop zones covering the elements selected by the selector
          // we generate overlay drop zones only to get an idea of where the snippet are, the drop
          activate_overlay_zones: function(selector){
 -            var $targets = this.dom_filter(selector);
 +            var $targets = typeof selector === "string" ? this.$wrapwrap.find(selector) : selector;
              var self = this;
  
 -            if (typeof selector !== 'string' && !$targets.length) {
 -                console.debug( "A good node must have a [data-oe-model] attribute or must have at least one parent with [data-oe-model] attribute.");
 -                console.debug( "Wrong node(s): ", selector);
 -            }
 -
              function is_visible($el){
                  return     $el.css('display')    != 'none'
                          && $el.css('opacity')    != '0'
                      $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () {
                          self.cover_target($zone, $target);
                      });
 -                    $('body').on("resize", function () {
 -                        self.cover_target($zone, $target);
 -                    });
 +                    var resize = function () {
 +                        if ($zone.parent().length) {
 +                            self.cover_target($zone, $target);
 +                        } else {
 +                            $('body').off("resize", resize);
 +                        }
 +                    };
 +                    $('body').on("resize", resize);
                  }
                  self.cover_target($target.data('overlay'), $target);
              });
      website.snippet.options = {};
      website.snippet.Option = openerp.Class.extend({
          // initialisation (don't overwrite)
 -        init: function (BuildingBlock, editor, $target, snippet_id) {
 +        init: function (BuildingBlock, editor, $target, option_id) {
              this.BuildingBlock = BuildingBlock;
              this.editor = editor;
              this.$target = $target;
 +            var option = website.snippet.templateOptions[option_id];
              var styles = this.$target.data("snippet-option-ids") || {};
 -            styles[snippet_id] = this;
 +            styles[option_id] = this;
              this.$target.data("snippet-option-ids", styles);
              this.$overlay = this.$target.data('overlay') || $('<div>');
 -            this['snippet-option-id'] = snippet_id;
 -            var $option = website.snippet.templateOptions[snippet_id].$el;
 -            this.$el = $option.find(">li").clone();
 -            this.data = $option.data();
 -
 -            this.required = this.$el.data("required");
 +            this.option= option_id;
 +            this.$el = option.$el.find(">li").clone();
 +            this.data = option.$el.data();
  
              this.set_active();
 -            this.$el.find('li[data-value] a').on('mouseenter mouseleave click', _.bind(this._mouse, this));
 -            this.$el.not(':not([data-value])').find("a").on('mouseenter mouseleave click', _.bind(this._mouse, this));
 -            this.$target.on('snippet-style-reset', _.bind(this.set_active, this));
 +            this.$target.on('snippet-option-reset', _.bind(this.set_active, this));
 +            this._bind_li_menu();
  
              this.start();
          },
 -        _mouse: function (event) {
 -            var self = this;
  
 -            if (event.type === 'mouseleave') {
 -                if (!this.over) return;
 -                this.over = false;
 -            } else if (event.type === 'click') {
 -                this.over = false;
 -            }else {
 -                this.over = true;
 -            }
 +        _bind_li_menu: function () {
 +            this.$el.filter("li:hasData").find('a:first')
 +                .off('mouseenter click')
 +                .on('mouseenter click', _.bind(this._mouse, this));
  
 -            var $prev, $next;
 -            if (event.type === 'mouseleave') {
 -                $prev = $(event.currentTarget).parent();
 -                $next = this.$el.find("li[data-value].active");
 -            } else {
 -                $prev = this.$el.find("li[data-value].active");
 -                $next = $(event.currentTarget).parent();
 -            }
 -            if (!$prev.length) {
 -                $prev = false;
 -            }
 -            if ($prev && $prev[0] === $next[0]) {
 -                $next = false;
 -                if (this.required) {
 -                    return;
 -                }
 -            }
 +            this.$el
 +                .off('mouseenter click', "li:hasData a")
 +                .on('mouseenter click', "li:hasData a", _.bind(this._mouse, this));
  
 -            var np = {'$next': $next, '$prev': $prev};
 +            this.$el.closest("ul").add(this.$el)
 +                .off('mouseleave')
 +                .on('mouseleave', _.bind(this.reset, this));
  
 +            this.$el
 +                .off('mouseleave', "ul")
 +                .on('mouseleave', "ul", _.bind(this.reset, this));
 +
 +            this.reset_methods = [];
 +            this.reset_time = null;
 +        },
 +
 +        /**
 +         * this method handles mouse:over and mouse:leave on the snippet editor menu
 +         */
 +         _time_mouseleave: null,
 +        _mouse: function (event) {
 +            var $next = $(event.currentTarget).parent();
 +
 +            // triggers preview or apply methods if a menu item has been clicked
 +            this.select(event.type === "click" ? "click" : "over", $next);
              if (event.type === 'click') {
 -                setTimeout(function () {
 -                    self.set_active();
 -                    self.$target.trigger("snippet-style-change", [self, np]);
 -                },0);
 -                this.select({'$next': $next, '$prev': $prev});
 +                this.set_active();
 +                this.$target.trigger("snippet-option-change", [this]);
              } else {
 -                setTimeout(function () {
 -                    self.$target.trigger("snippet-style-preview", [self, np]);
 -                },0);
 -                this.preview(np);
 +                this.$target.trigger("snippet-option-preview", [this]);
              }
          },
 -        /* set_active
 +        /* 
          *  select and set item active or not (add highlight item and his parents)
          *  called before start
          */
          set_active: function () {
 -            var self = this;
 -            this.$el.find('li').removeClass("active");
 -            var $active = this.$el.find('li[data-value]')
 -                .filter(function () {
 -                    var $li = $(this);
 -                    return  ($li.data('value') && self.$target.hasClass($li.data('value')));
 -                })
 -                .first()
 +            var classes = _.uniq((this.$target.attr("class") || '').split(/\s+/));
 +            this.$el.find('[data-toggle_class], [data-select_class]')
 +                .add(this.$el)
 +                .filter('[data-toggle_class], [data-select_class]')
 +                .removeClass("active")
 +                .filter('[data-toggle_class="' + classes.join('"], [data-toggle_class="') + '"] ,'+
 +                    '[data-select_class="' + classes.join('"], [data-select_class="') + '"]')
                  .addClass("active");
 -            this.$el.find('li:has(li[data-value].active)').addClass("active");
          },
  
          start: function () {
          },
  
 -        onFocus : function () {
 +        on_focus : function () {
 +            this._bind_li_menu();
          },
  
 -        onBlur : function () {
 +        on_blur : function () {
          },
  
          on_clone: function ($clone) {
          drop_and_build_snippet: function () {
          },
  
 -        select: function (np) {
 +        reset: function (event) {
              var self = this;
 -            // add or remove html class
 -            if (np.$prev && this.required) {
 -                this.$target.removeClass(np.$prev.data('value' || ""));
 +            var lis = self.$el.add(self.$el.find('li')).filter('.active').get();
 +            lis.reverse();
 +            _.each(lis, function (li) {
 +                var $li = $(li);
 +                for (var k in self.reset_methods) {
 +                    var method = self.reset_methods[k];
 +                    if ($li.is('[data-'+method+']') || $li.closest('[data-'+method+']').size()) {
 +                        delete self.reset_methods[k];
 +                    }
 +                }
 +                self.select("reset", $li);
 +            });
 +
 +            for (var k in self.reset_methods) {
 +                var method = self.reset_methods[k];
 +                if (method) {
 +                    self[method]("reset", null);
 +                }
 +            }
 +            self.reset_methods = [];
 +            self.$target.trigger("snippet-option-reset", [this]);
 +        },
 +
 +        // call data-method args as method
 +        select: function (type, $li) {
 +            var self = this,
 +                $methods = [],
 +                el = $li[0],
 +                $el;
 +            clearTimeout(this.reset_time);
 +
 +            function filter (k) { return k !== 'oeId' && k !== 'oeModel' && k !== 'oeField' && k !== 'oeXpath' && k !== 'oeSourceId';}
 +            function hasData(el) {
 +                for (var k in el.dataset) {
 +                    if (filter (k)) {
 +                        return true;
 +                    }
 +                }
 +                return false;
 +            }
 +            function method(el) {
 +                var data = {};
 +                for (var k in el.dataset) {
 +                    if (filter (k)) {
 +                        data[k] = el.dataset[k];
 +                    }
 +                }
 +                return data;
              }
 -            if (np.$next) {
 -                this.$target.addClass(np.$next.data('value') || "");
 +
 +            while (el && this.$el.is(el) || _.some(this.$el.map(function () {return $.contains(this, el);}).get()) ) {
 +                if (hasData(el)) {
 +                    $methods.push(el);
 +                }
 +                el = el.parentNode;
              }
 +
 +            $methods.reverse();
 +
 +            _.each($methods, function (el) {
 +                var $el = $(el);
 +                var methods = method(el);
 +
 +                for (var k in methods) {
 +                    if (self[k]) {
 +                        if (type !== "reset" && self.reset_methods.indexOf(k) === -1) {
 +                            self.reset_methods.push(k);
 +                        }
 +                        self[k](type, methods[k], $el);
 +                    } else {
 +                        console.error("'"+self.option+"' snippet have not method '"+k+"'");
 +                    }
 +                }
 +            });
          },
  
 -        preview: function (np) {
 -            var self = this;
 +        // default method for snippet
 +        toggle_class: function (type, value, $li) {
 +            var $lis = this.$el.find('[data-toggle_class]').add(this.$el).filter('[data-toggle_class]');
  
 -            // add or remove html class
 -            if (np.$prev) {
 -                this.$target.removeClass(np.$prev.data('value') || "");
 +            function map ($lis) {
 +                return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" ");
              }
 -            if (np.$next) {
 -                this.$target.addClass(np.$next.data('value') || "");
 +            var classes = map($lis);
 +            var active_classes = map($lis.filter('.active, :has(.active)'));
 +
 +            this.$target.removeClass(classes);
 +            this.$target.addClass(active_classes);
 +
 +            if (type !== 'reset') {
 +                this.$target.toggleClass(value);
              }
          },
 +        select_class: function (type, value, $li) {
 +            var $lis = this.$el.find('[data-select_class]').add(this.$el).filter('[data-select_class]');
  
 -        clean_for_save: dummy
 -    });
 +            var classes = $lis.map(function () {return $(this).data('select_class');}).get();
  
 -    website.snippet.options.background = website.snippet.Option.extend({
 -        _get_bg: function () {
 -            return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
 +            this.$target.removeClass(classes.join(" "));
 +            if(value) this.$target.addClass(value);
          },
 -        _set_bg: function (src) {
 -            this.$target.css("background-image", src && src !== "" ? 'url(' + src + ')' : "");
 +        eval: function (type, value, $li) {
 +            var fn = new Function("node", "type", "value", "$li", value);
 +            fn.call(this, this, type, value, $li);
          },
 +
 +        clean_for_save: dummy
 +    });
 +    website.snippet.options.background = website.snippet.Option.extend({
          start: function () {
              this._super();
 -            var src = this._get_bg();
 -            this.$el.find("li[data-value].active.oe_custom_bg").data("src", src);
 +            var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
 +            if (this.$target.hasClass('oe_custom_bg')) {
 +                this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
 +            }
          },
 -        select: function(np) {
 -            var self = this;
 -            this._super(np);
 -            if (np.$next) {
 -                if (np.$next.hasClass("oe_custom_bg")) {
 -                    var $image = $('<img class="hidden"/>');
 -                    $image.attr("src", np.$prev ? np.$prev.data("src") : '');
 -                    $image.appendTo(self.$target);
 -
 -                    self.element = new CKEDITOR.dom.element($image[0]);
 -                    var editor = new website.editor.MediaDialog(self, self.element);
 -                    editor.appendTo(document.body);
 -                    editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
 -
 -                    $image.on('saved', self, function (o) {
 -                        var src = $image.attr("src");
 -                        self._set_bg(src);
 -                        np.$next.data("src", src);
 -                        self.$target.trigger("snippet-style-change", [self, np]);
 -                        $image.remove();
 -                    });
 -                    editor.on('cancel', self, function () {
 -                        if (!np.$prev || np.$prev.data("src") === "") {
 -                            self.$target.removeClass(np.$next.data("value"));
 -                            self.$target.trigger("snippet-style-change", [self, np]);
 -                        }
 -                        $image.remove();
 -                    });
 -                } else {
 -                    this._set_bg(np.$next.data("src"));
 -                }
 +        background: function(type, value, $li) {
 +            if (value && value.length) {
 +                this.$target.css("background-image", 'url(' + value + ')');
 +                this.$target.addClass("oe_img_bg");
              } else {
 -                this._set_bg(false);
 -                this.$target.removeClass(np.$prev.data("value"));
 +                this.$target.css("background-image", "");
 +                this.$target.removeClass("oe_img_bg").removeClass("oe_custom_bg");
              }
          },
 -        preview: function (np) {
 -            this._super(np);
 -            if (np.$next) {
 -                this._set_bg(np.$next.data("src"));
 -            }
 +        choose_image: function(type, value, $li) {
 +            if(type !== "click") return;
 +
 +            var self = this;
 +            var $image = $('<img class="hidden"/>');
 +            $image.attr("src", value);
 +            $image.appendTo(self.$target);
 +
 +            self.element = new CKEDITOR.dom.element($image[0]);
 +            var editor = new website.editor.MediaDialog(self, self.element);
 +            editor.appendTo(document.body);
 +            editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
 +
 +            $image.on('saved', self, function (o) {
 +                var value = $image.attr("src");
 +                self.$target.css("background-image", 'url(' + value + ')');
 +                self.$el.find('li[data-choose_image]').data("background", value).attr("data-background", value);
 +                self.$target.trigger("snippet-option-change", [self]);
 +                $image.remove();
 +                self.$target.addClass('oe_custom_bg oe_img_bg');
 +                self.set_active();
 +            });
 +            editor.on('cancel', self, function () {
 +                self.$target.trigger("snippet-option-change", [self]);
 +                $image.remove();
 +            });
          },
          set_active: function () {
              var self = this;
 -            var bg = self.$target.css("background-image");
 -            this.$el.find('li').removeClass("active");
 -            this.$el.find('li').removeClass("btn-primary");
 -            var $active = this.$el.find('li[data-value]')
 -                .filter(function () {
 -                    var $li = $(this);
 -                    return  ($li.data('src') && bg.indexOf($li.data('src')) >= 0) ||
 -                            (!$li.data('src') && self.$target.hasClass($li.data('value')));
 -                })
 -                .first();
 -            if (!$active.length) {
 -                $active = this.$target.css("background-image") !== 'none' ?
 -                    this.$el.find('li[data-value].oe_custom_bg') :
 -                    this.$el.find('li[data-value=""]');
 +            var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
 +            this._super();
 +
 +            this.$el.find('li[data-background]:not([data-background=""])')
 +                .removeClass("active")
 +                .each(function () {
 +                    var background = $(this).data("background") || $(this).attr("data-background");
 +                    if ((src.length && background.length && src.indexOf(background) !== -1) || (!src.length && !background.length)) {
 +                        $(this).addClass("active");
 +                    }
 +                });
 +
 +            if (!this.$el.find('li[data-background].active').size()) {
 +                this.$el.find('li[data-background=""]:not([data-choose_image])').addClass("active");
 +            } else {
 +                this.$el.find('li[data-background=""]:not([data-choose_image])').removeClass("active");
              }
 +        }
 +    });
 +
 +    website.snippet.options.colorpicker = website.snippet.Option.extend({
 +        start: function () {
 +            var self = this;
 +            var res = this._super();
  
 -            //don't set active on an OpenDialog link, else it not possible to click on it again after.
 -            // TODO in Saas-4 - Once bootstrap is in less
 -            //      - add a class active-style to get the same display but without the active behaviour used by bootstrap in JS.
 -            var classStr = _.string.contains($active[0].className, "oe_custom_bg") ? "btn-primary" : "active";
 -            $active.addClass(classStr);
 -            this.$el.find('li:has(li[data-value].active)').addClass(classStr);
 +            this.$el.find('li').append( openerp.qweb.render('website.colorpicker') );
 +
 +            var classes = [];
 +            this.$el.find("table.colorpicker td > *").map(function () {
 +                var $color = $(this);
 +                var color = $color.attr("class");
 +                if (self.$target.hasClass(color)) {
 +                    self.color = color;
 +                    $color.parent().addClass("selected");
 +                }
 +                classes.push(color);
 +            });
 +            this.classes = classes.join(" ");
 +
 +            this.bind_events();
 +            return res;
 +        },
 +        bind_events: function () {
 +            var self = this;
 +            var $td = this.$el.find("table.colorpicker td");
 +            var $colors = $td.children();
 +            $colors
 +                .mouseenter(function () {
 +                    self.$target.removeClass(self.classes).addClass($(this).attr("class"));
 +                })
 +                .mouseleave(function () {
 +                    self.$target.removeClass(self.classes)
 +                        .addClass($td.filter(".selected").children().attr("class"));
 +                })
 +                .click(function () {
 +                    $td.removeClass("selected");
 +                    $(this).parent().addClass("selected");
 +                });
          }
      });
  
                  self.$target.carousel(+$(this).data('slide-to')); });
  
              this.$target.attr('contentEditable', 'false');
-             this.$target.find('.oe_structure, .content>.row, [data-slide]').attr('contentEditable', 'true');
+             this.$target.find('.oe_structure, .content.row, [data-slide]').attr('contentEditable', 'true');
          },
          clean_for_save: function () {
              this._super();
 -            this.$target.find(".item").removeClass("next prev left right active");
 -            this.$target.find(".item:first").addClass("active");
 -            this.$indicators.find('li').removeClass('active');
 -            this.$indicators.find('li:first').addClass('active');
 +            this.$target.find(".item").removeClass("next prev left right active")
 +                .first().addClass("active");
 +            this.$indicators.find('li').removeClass('active')
 +                .first().addClass("active");
          },
          start : function () {
              var self = this;
              this.id = this.$target.attr("id");
              this.$inner = this.$target.find('.carousel-inner');
              this.$indicators = this.$target.find('.carousel-indicators');
              this.$target.carousel('pause');
              this.rebind_event();
          },
 -        on_add_slide: function () {
 +        add_slide: function (type, value) {
 +            if(type !== "click") return;
 +
              var self = this;
              var cycle = this.$inner.find('.item').length;
              var $active = this.$inner.find('.item.active, .item.prev, .item.next').first();
              this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden");
              this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
  
-             var $clone = this.$target.find(".item.active").clone();
+             // clone the best candidate from template to use new features
+             var $snippets = this.BuildingBlock.$snippets.find('.oe_snippet_body.carousel');
+             var point = 0;
+             var selection;
+             var className = _.compact(this.$target.attr("class").split(" "));
+             $snippets.each(function () {
+                 var len = _.intersection(_.compact(this.className.split(" ")), className).length;
+                 if (len > point) {
+                     point = len;
+                     selection = this;
+                 }
+             });
+             var $clone = $(selection).find('.item:first').clone();
  
              // insert
              $clone.removeClass('active').insertAfter($active);
              },0);
              return $clone;
          },
 -        on_remove_slide: function () {
 +        remove_slide: function (type, value) {
 +            if(type !== "click") return;
 +
              if (this.remove_process) {
                  return;
              }
                  this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden");
              }
          },
 +        interval : function(type, value) {
 +            this.$target.attr("data-interval", value);
 +        },
 +        set_active: function () {
 +            this.$el.find('li[data-interval]').removeClass("active")
 +                .filter('li[data-interval='+this.$target.attr("data-interval")+']').addClass("active");
 +        },
      });
      website.snippet.options.carousel = website.snippet.options.slider.extend({
          getSize: function () {
          },
          clean_for_save: function () {
              this._super();
 -            this.$target.css("background-image", "");
 -            this.$target.removeClass(this._class);
 +            this.$target.removeClass('oe_img_bg ' + this._class).css("background-image", "");
          },
          load_style_options : function () {
              this._super();
 -            $(".snippet-style-size li[data-value='']").remove();
 +            $(".snippet-option-size li[data-value='']").remove();
          },
          start : function () {
              var self = this;
                  if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c;
                  return self._class || "";
              };
 -            this.$target.on('snippet-style-change snippet-style-preview', function (event, style, np) {
 -                var $active = self.$target.find(".item.active");
 -                if (style['snippet-option-id'] === "size") return;
 -                if (style['snippet-option-id'] === "background") {
 -                    $active.css("background-image", self.$target.css("background-image"));
 -                }
 -                if (np.$prev) {
 -                    $active.removeClass(np.$prev.data("value"));
 +            this.$target.on('slid.bs.carousel', function () {
 +                if(self.editor && self.editor.styles.background) {
 +                    self.editor.styles.background.$target = self.$target.find(".item.active");
 +                    self.editor.styles.background.set_active();
                  }
 -                if (np.$next) {
 -                    $active.addClass(np.$next.data("value"));
 -                    add_class(np.$next.data("value"));
 -                }
 -            });
 -            this.$target.on('slid', function () { // slide.bs.carousel
 -                var $active = self.$target.find(".item.active");
 -                self.$target
 -                    .css("background-image", $active.css("background-image"))
 -                    .removeClass(add_class($active.attr("class")))
 -                    .addClass($active.attr("class"))
 -                    .trigger("snippet-style-reset");
 -
                  self.$target.carousel("pause");
              });
 -            this.$target.trigger('slid');
 +            this.$target.trigger('slid.bs.carousel');
          },
 -        on_add_slide: function () {
 -            var $clone = this._super();
 +        add_slide: function (type, data) {
 +            if(type !== "click") return;
  
 +            var $clone = this._super(type, data);
              // choose an other background
              var bg = this.$target.data("snippet-option-ids").background;
              if (!bg) return $clone;
  
 -            var $styles = bg.$el.find("li[data-value]:not(.oe_custom_bg)");
 -            var styles_index = $styles.index($styles.filter(".active")[0]);
 -            $styles.removeClass("active");
 -            var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]);
 +            var $styles = bg.$el.find("li[data-background]");
 +            var $select = $styles.filter(".active").removeClass("active").next("li[data-background]");
 +            if (!$select.length) {
 +                $select = $styles.first();
 +            }
              $select.addClass("active");
 -            $clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : "");
 -            $clone.addClass($select.data("value") || "");
 +            $clone.css("background-image", $select.data("background") ? "url('"+ $select.data("background") +"')" : "");
  
              return $clone;
          },
              var self = this;
              this.$target.find('.carousel-control').off('click').on('click', function () {
                  self.$target.carousel( $(this).data('slide')); });
-             this.$target.find('.carousel-image, .carousel-inner .content > div').attr('contentEditable', 'true');
-             this.$target.find('.carousel-image').attr('attributeEditable', 'true');
              this._super();
+             /* Fix: backward compatibility saas-3 */
+             this.$target.find('.item.text_image, .item.image_text, .item.text_only').find('.container > .carousel-caption > div, .container > img.carousel-image').attr('contentEditable', 'true');
          },
      });
 -
      website.snippet.options.marginAndResize = website.snippet.Option.extend({
          start: function () {
              var self = this;
              return this.grid;
          },
  
 -        onFocus : function () {
 +        on_focus : function () {
              this._super();
              this.change_cursor();
          },
          hide_remove_button: function() {
              this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length);
          },
 -        onFocus : function () {
 +        on_focus : function () {
              this._super();
              this.hide_remove_button();
          },
              return false;
          },
          on_remove: function () {
 -            if (!this.$target.siblings().length) {
 -                var $parent = this.$target.parents(".row:first");
 -                if($parent.find("[class*='col-md']").length > 1) {
 -                    return false;
 -                } else {
 -                    if (!$parent.data("snippet-editor")) {
 -                        this.BuildingBlock.create_overlay($parent);
 -                    }
 -                    $parent.data("snippet-editor").on_remove();
 -                }
 -            }
              this._super();
              this.hide_remove_button();
 -            return false;
          },
          on_resize: function (compass, beginClass, current) {
              if (compass === 'w') {
          },
      });
  
 -    website.snippet.options["resize"] = website.snippet.options.marginAndResize.extend({
 +    website.snippet.options.resize = website.snippet.options.marginAndResize.extend({
          getSize: function () {
              this.grid = this._super();
              this.grid.size = 8;
                  this.$target.data("snippet-view", new website.snippet.animationRegistry.parallax(this.$target));
              }
              this.scroll();
 -            this.$target.on('snippet-style-change snippet-style-preview', function () {
 +            this.$target.on('snippet-option-change snippet-option-preview', function () {
                  self.$target.data("snippet-view").set_values();
              });
              this.$target.attr('contentEditable', 'false');
  
              this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true');
          },
 -        scroll: function () {
 -            var self = this;
 -            var $ul = this.$el.find('ul[name="parallax-scroll"]');
 -            var $li = $ul.find("li");
 -            var speed = this.$target.data('scroll-background-ratio') || 0.6 ;
 -            $ul.find('[data-value="' + speed + '"]').addClass('active');
 -            $li.on('click', function (event) {
 -                $li.removeClass("active");
 -                $(this).addClass("active");
 -                var speed =  $(this).data('value');
 -                self.$target.attr('data-scroll-background-ratio', speed);
 -                self.$target.data("snippet-view").set_values();
 -                return false;
 -            });
 +        scroll: function (type, value) {
 +            this.$target.attr('data-scroll-background-ratio', value);
              this.$target.data("snippet-view").set_values();
          },
 +        set_active: function () {
 +            var value = this.$target.attr('data-scroll-background-ratio') || 0;
 +            this.$el.find('[data-scroll]').removeClass("active")
 +                .filter('[data-scroll="' + (this.$target.attr('data-scroll-background-ratio') || 0) + '"]').addClass("active");
 +        },
          clean_for_save: function () {
              this._super();
              this.$target.find(".parallax")
          start: function () {
              var self = this;
              this._super();
 -
 -            this.$el.find(".clear-style").click(function (event) {
 -                self.$target.removeClass("fa-spin").attr("style", "");
 -                self.resetTransfo();
 -            });
 -
 -            this.$el.find(".style").click(function (event) {
 -                var settings = self.$target.data("transfo").settings;
 -                self.$target.transfo({ hide: (settings.hide = !settings.hide) });
 -            });
 -
              this.$overlay.find('.oe_snippet_clone, .oe_handles').addClass('hidden');
 -
              this.$overlay.find('[data-toggle="dropdown"]')
                  .on("mousedown", function () {
                      self.$target.transfo("hide");
                  });
          },
 +        style: function (type, value) {
 +            if (type !== 'click') return;
 +            var settings = this.$target.data("transfo").settings;
 +            this.$target.transfo({ hide: (settings.hide = !settings.hide) });
 +        },
 +        clear_style: function (type, value) {
 +            if (type !== 'click') return;
 +            this.$target.removeClass("fa-spin").attr("style", "");
 +            this.resetTransfo();
 +        },
          resetTransfo: function () {
              var self = this;
              this.$target.transfo("destroy");
                  })
                  .mouseover();
          },
 -        onFocus : function () {
 +        on_focus : function () {
              this.resetTransfo();
          },
 -        onBlur : function () {
 +        on_blur : function () {
              this.$target.transfo("hide");
          },
      });
              website.snippet.start_animation(true, this.$target);
  
              $(document.body).on("media-saved", self, function (event, prev , item) {
 -                self.editor.onBlur();
 +                self.editor.on_blur();
                  self.BuildingBlock.make_active(false);
                  if (self.$target.parent().data("oe-field") !== "image") {
                      self.BuildingBlock.make_active($(item));
                  }
              });
 -
 -            this.$el.find(".edition").click(function (event) {
 -                event.preventDefault();
 -                event.stopPropagation();
 -                self.element = new CKEDITOR.dom.element(self.$target[0]);
 -                new website.editor.MediaDialog(self, self.element).appendTo(document.body);
 -            });
          },
 -        onFocus : function () {
 +        edition: function (type, value) {
 +            if(type !== "click") return;
 +            this.element = new CKEDITOR.dom.element(this.$target[0]);
 +            new website.editor.MediaDialog(this, this.element).appendTo(document.body);
 +        },
 +        on_focus : function () {
              var self = this;
              if (this.$target.parent().data("oe-field") === "image") {
                  this.$overlay.addClass("hidden");
          },
      });
  
 -
      website.snippet.Editor = openerp.Class.extend({
          init: function (BuildingBlock, dom) {
              this.BuildingBlock = BuildingBlock;
              this.$target = $(dom);
              this.$overlay = this.$target.data('overlay');
 -            this.snippet_id = this.$target.data("snippet-id");
 -            this._readXMLData();
              this.load_style_options();
              this.get_parent_block();
              this.start();
          },
  
 -        /*
 -        *  _readXMLData
 -        *  Read data XML and set value into:
 -        *  this.$el :
 -        *       all xml data
 -        *  this.$overlay :
 -        *       Dom hover the $target who content options
 -        */
 -        _readXMLData: function() {
 -            var self = this;
 -            if(this && this.BuildingBlock && this.BuildingBlock.$snippets) {
 -                this.$el = this.BuildingBlock.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
 -            }
 -            var $options = this.$overlay.find(".oe_overlay_options");
 -            if ($options.find(".oe_options ul li").length) {
 -                $options.find(".oe_options").removeClass("hidden");
 -            }
 -        },
 -
 -
          // activate drag and drop for the snippets in the snippet toolbar
          _drag_and_drop: function(){
              var self = this;
              self.BuildingBlock.activate_insertion_zones({
                  siblings: self.selector_siblings,
                  children: self.selector_children,
 -                vertical_children: self.selector_vertical_children,
              });
  
              $("body").addClass('move-important');
              this.styles = {};
              this.selector_siblings = [];
              this.selector_children = [];
 -            this.selector_vertical_children = [];
 -            _.each(website.snippet.templateOptions, function (val) {
 +            _.each(website.snippet.templateOptions, function (val, option_id) {
                  if (!self.$target.is(val.selector)) {
                      return;
                  }
 -                if (val['selector-siblings']) self.selector_siblings.push(val['selector-siblings']);
 -                if (val['selector-children']) self.selector_children.push(val['selector-children']);
 -                if (val['selector-vertical-children']) self.selector_vertical_children.push(val['selector-vertical-children']);
 -
 -                var style = val['snippet-option-id'];
 -                var Editor = website.snippet.options[style] || website.snippet.Option;
 -                var editor = self.styles[style] = new Editor(self.BuildingBlock, self, self.$target, style);
 -                $ul.append(editor.$el.addClass("snippet-style-" + style));
 +                if (val['drop-near']) self.selector_siblings.push(val['drop-near']);
 +                if (val['drop-in']) self.selector_children.push(val['drop-in']);
 +
 +                var option = val['option'];
 +                var Editor = website.snippet.options[option] || website.snippet.Option;
 +                var editor = self.styles[option] = new Editor(self.BuildingBlock, self, self.$target, option_id);
 +                $ul.append(editor.$el.addClass("snippet-option-" + option));
              });
              this.selector_siblings = this.selector_siblings.join(",");
              if (this.selector_siblings === "")
              this.selector_children = this.selector_children.join(",");
              if (this.selector_children === "")
                  this.selector_children = false;
 -            this.selector_vertical_children = this.selector_vertical_children.join(",");
 -            if (this.selector_vertical_children === "")
 -                this.selector_vertical_children = false;
  
 -            if (!this.selector_siblings && !this.selector_children && !this.selector_vertical_children) {
 -                this.$overlay.find(".oe_snippet_move").addClass('hidden');
 +            if (!this.selector_siblings && !this.selector_children) {
 +                this.$overlay.find(".oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove").addClass('hidden');
              }
  
  
          },
  
          on_remove: function () {
 -            this.onBlur();
 +            this.on_blur();
              var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0));
              for (var i in this.styles){
                  this.styles[i].on_remove();
              }
              delete this.BuildingBlock.snippets[index];
 +
 +            // remove node and his empty
 +            var parent,
 +                node = this.$target.parent()[0];
 +
              this.$target.remove();
 +            function check(node) {
 +                if ($(node).outerHeight() > 8) {
 +                    return false;
 +                }
 +                for (var k=0; k<node.children.length; k++) {
 +                    if (node.children[k].tagName || node.children[k].textContent.match(/[^\s]/)) {
 +                        return false;
 +                    }
 +                }
 +                return true;
 +            }
 +            while (check(node)) {
 +                parent = node.parentNode;
 +                parent.removeChild(node);
 +                node = parent;
 +            }
 +
              this.$overlay.remove();
              return false;
          },
              }
          },
  
 -        /* onFocus
 +        /* on_focus
          *  This method is called when the user click inside the snippet in the dom
          */
 -        onFocus : function () {
 +        on_focus : function () {
              this.$overlay.addClass('oe_active');
              for (var i in this.styles){
 -                this.styles[i].onFocus();
 +                this.styles[i].on_focus();
              }
          },
  
 -        /* onFocus
 +        /* on_focus
          *  This method is called when the user click outside the snippet in the dom, after a focus
          */
 -        onBlur : function () {
 +        on_blur : function () {
              for (var i in this.styles){
 -                this.styles[i].onBlur();
 +                this.styles[i].on_blur();
              }
              this.$overlay.removeClass('oe_active');
          },
@@@ -22,7 -22,7 +22,7 @@@
                          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
                          <h3 class="modal-title"><t t-esc="title"/></h3>
                      </div>
-                     <div class="modal-body"><t t-raw="__content__"/></div>
+                     <div class="modal-body"><t t-raw="0"/></div>
                      <div class="modal-footer">
                          <button type="button" class="btn btn-primary save">Save</button>
                          <button type="button" class="btn hidden wait" disabled="disabled"/>
                    class="form-inline">
                  <div class="well">
                      <div class="form-group pull-left">
 -                        <input type="file" name="upload" accept="image/*" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/>
 +                        <input type="file" name="upload" accept="image/*" multiple="multiple" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/>
                          <input type="hidden" name="disable_optimization" value=""/>
                          <div class="btn-group">
                              <button type="button" class="btn btn-primary filepicker">Upload an image from your computer</button>
  
  <div id="snippet_structure" class="tab-pane active">
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_banner.png"/>
 -            <span class="oe_snippet_thumbnail_title">Banner</span>
 -        </div>
 -        <div id="myCarousel" class="oe_snippet_body carousel slide mb32" style="height: 320px;">
 +    <div name="Banner" class="o_block_banner">
 +        <div id="myCarousel" class="carousel slide mb32" data-interval="10000" style="height: 320px;">
              <!-- Indicators -->
              <ol class="carousel-indicators hidden">
                  <li data-target="#myCarousel" data-slide-to="0" class="active"></li>
              </ol>
              <div class="carousel-inner">
-                 <div class="item image_text active" style="background-image: url('/website/static/src/img/banner/color_splash.jpg')">
+                 <div class="item active" style="background-image: url('/website/static/src/img/banner/color_splash.jpg')">
                      <div class="container">
                          <div class="row content">
                              <div class="carousel-content col-md-6 col-sm-12">
@@@ -31,7 -35,7 +31,7 @@@
                                      </p>
                              </div>
                              <span class="carousel-img col-md-6 hidden-sm hidden-xs">
-                                 <img class="carousel-image img-responsive" src="/website/static/src/img/banner/banner_picture.png" alt="Banner Odoo Image"/>
+                                 <img class="img-responsive" src="/website/static/src/img/banner/banner_picture.png" alt="Banner Odoo Image"/>
                              </span> 
                          </div>
                      </div>
          </div>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_text_image.png"/>
 -            <span class="oe_snippet_thumbnail_title">Text-Image</span>
 -        </div>
 -        <section class="oe_snippet_body">
 +    <div name="Text-Image" class="o_block_text_image">
 +        <section>
              <div class="container">
                  <div class="row">
                      <div class="col-md-6 mt16">
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_image_text.png"/>
 -            <span class="oe_snippet_thumbnail_title">Image-Text</span>
 -        </div>
 -        <section class="oe_snippet_body">
 +    <div name="Image-Text" class="o_block_image_text">
 +        <section>
              <div class="container">
                  <div class="row">
                      <div class="col-md-6 mt16">
          </section>
      </div>
  
+     <div name="Image-Text" class="o_block_image_float">
+         <div class="oe_snippet_thumbnail">
+             <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_text_image.png"/>
+             <span class="oe_snippet_thumbnail_title">Image-Floating</span>
+         </div>
+         <section class="oe_snippet_body para_large">
+             <div class="container">
+                 <div class="row">
+                     <div class="col-md-12 mb16 mt16">
+                         <div class="o_image_floating o_margin_l pull-right">
+                             <div class="o_container">
+                                 <img class="img img-rounded img-responsive" src="/website/static/src/img/odoo.jpg"/>
+                                 <mark class="text-center"><a href="#"><strong>Click Here</strong></a></mark>
+                             </div>
+                             <div class="o_footer">
+                                 <small class="text-muted">A great way to catch your reader's attention is to tell a story. Everything you consider writing can be told as a story.</small>
+                             </div>
+                         </div>
+                         <h3>What a day it was yesterday - such a big day for us!</h3>
+                         <p style="text-align: justify;">
+                             In case you still feel a bit puzzled about all
+                             of our yesterday's announcements, here is a little
+                             summary for you. <br/>
+                             We have decided to change the
+                             name because <b>"OpenERP"</b> didn't reflect the offering
+                             of the company anymore. With our newest apps, such
+                             as Website Builder, PoS or eCommerce, we have
+                             moved beyond the ERP territory. <br/>
+                             But <u>don't worry</u>, <b>Odoo</b> is and always will be
+                             fully open source. You can read more about the new name here.
+                             We have also prepared a short FAQ to explain all these changes
+                             to all of you. <br/>
+                         </p>
+                         <h3>Discover more about Odoo</h3>
+                         <p style="text-align: justify;">
+                             With Odoo's fully integrated software, you can easily manage your
+                             meetings, schedule business calls, create recurring meetings,
+                             synchronize your agenda and easily keep in touch with your colleagues,
+                             partners and other people involved in projects or business discussions.
+                             <br/><br/><a href="#">Check now and discover more today!</a>
+                         </p>
+                     </div>
+                 </div>
+             </div>
+         </section>
+     </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_jumbotron.png"/>
 -            <span class="oe_snippet_thumbnail_title">Big Message</span>
 -        </div>
 -        <section class="oe_snippet_body jumbotron mt16 mb16">
 +    <div name="Big Message" class="o_block_jumbotron">
 +        <section class="jumbotron mt16 mb16">
              <div class="container">
                  <h1>Sell Online. Easily.</h1>
                  <p>
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_text_block.png"/>
 -            <span class="oe_snippet_thumbnail_title">Text Block</span>
 -        </div>
 -        <section class="oe_snippet_body mt16 mb16">
 +    <div name="Text Block" class="o_block_text_block">
 +        <section class="mt16 mb16">
              <div class="container">
                  <div class="row">
                      <div class="col-md-12 text-center mt16 mb32">
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_title.png"/>
 -            <span class="oe_snippet_thumbnail_title">Title</span>
 -        </div>
 -        <section class="oe_snippet_body">
 +    <div name="Title" class="o_block_title">
 +        <section>
              <div class="container">
                  <div class="row">
                      <div class="col-md-12">
          </section>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_features.png"/>
 -            <span class="oe_snippet_thumbnail_title">Features</span>
 -        </div>
 -        <section class="oe_snippet_body mb16">
 +    <div name="Features" class="o_block_features">
 +        <section class="mb16">
              <div class="container">
                  <div class="row mt16 mb16">
                      <div class="col-md-4 text-center">
          </section>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_big_picture.png"/>
 -            <span class="oe_snippet_thumbnail_title">Big Picture</span>
 -        </div>
 -        <section class="oe_snippet_body oe_dark mt16 mb16">
 +    <div name="Big Picture" class="o_block_big_picture">
 +        <section class="oe_dark mt16 mb16">
              <div class="container">
                  <div class="row">
                      <div class="col-md-12 text-center mt32 mb32">
          </section>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_three_columns.png"/>
 -            <span class="oe_snippet_thumbnail_title">Three Columns</span>
 -        </div>
 -        <section class="oe_snippet_body mt16 mb16">
 +    <div name="Three Columns" class="o_block_three_columns">
 +        <section class="mt16 mb16">
              <div class="container">
                  <div class="row">
                      <div class="col-md-4">
  
  <div id="snippet_content" class="tab-pane">
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_well.png"/>
 -            <span class="oe_snippet_thumbnail_title">Well</span>
 -        </div>
 -        <div class="oe_snippet_body well">
 +    <div name="Well" class="o_block_well">
 +        <div class="well">
              Explain the benefits you offer. Don't write about products or
              services here, write about solutions.
          </div>
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_quote.png"/>
 -            <span class="oe_snippet_thumbnail_title">Quote</span>
 -        </div>
 -        <blockquote class="oe_snippet_body">
 +    <div name="Quote" class="o_block_quote">
 +        <blockquote>
              <p>
                  Write a quote here from one of your customers. Quotes are a
                  great way to build confidence in your products or services.
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_panel.png"/>
 -            <span class="oe_snippet_thumbnail_title">Panel</span>
 -        </div>
 -        <div class="oe_snippet_body panel panel-default">
 +    <div name="Panel" class="o_block_panel">
 +        <div class="panel panel-default">
              <div class="panel-heading">
                  <h3 class="panel-title">Feature Title</h3>
              </div>
          </div>
      </div>
  
+     <div name="Image Floating" class="o_block_image_floating">
+         <div class="oe_snippet_thumbnail">
+             <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_text_image.png"/>
+             <span class="oe_snippet_thumbnail_title">Image Floating</span>
+         </div>
+         <div class="oe_snippet_body o_image_floating o_margin_l pull-right">
+             <div class="o_container">
+                 <img class="img img-rounded img-responsive" src="/website/static/src/img/library/ipad.png"/>
+                 <mark class="text-center"><a href="#"><strong>Click Here</strong></a></mark>
+             </div>
+             <div class="o_footer">
+                 <small class="text-muted">A great way to catch your reader's attention is to tell a story. Everything you consider writing can be told as a story.</small>
+             </div>
+         </div>
+     </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_separator.png"/>
 -            <span class="oe_snippet_thumbnail_title">Separator</span>
 -        </div>
 -        <hr class="oe_snippet_body"/>
 +    <div name="Separator" class="o_block_separator">
 +        <hr/>
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_button.png"/>
 -            <span class="oe_snippet_thumbnail_title">Share</span>
 -        </div>
 -        <div class="oe_snippet_body oe_share">
 +    <div name="Share" class="o_block_button">
 +        <div class="oe_share">
              <h3>
                  Share 
                  <a target="_Blank" class="oe_share_facebook" href="https://www.facebook.com/sharer/sharer.php?u={url}"><i class="fa fa-facebook-square"></i></a>
          </div>
      </div>
  
 +    <div name="Table" class="o_block_table">
 +        <section class="oe_snippet_body mt16 mb16">
 +            <div class="container">
 +                <div class="row">
 +                    <div class="col-md-8">
 +                        <table cellspacing="0" class="table_desc">
 +                            <tbody>
 +                                <tr><th class="table_heading" colspan="2">Description</th></tr>
 +                                <tr>
 +                                    <td></td>
 +                                    <td>(Press Tab to add a new row)</td>
 +                                </tr>
 +                            </tbody>
 +                        </table>
 +                    </div>
 +                    <div class="col-md-4">
 +                        <img class="img img-rounded img-responsive" src="/website/static/src/img/library/ipad.png"/>
 +                        <h4 class="mt16">Description</h4>
 +                        <p>
 +                            To add a related product list, reduce 
 +                            the size of these three columns using 
 +                            the right icon of each block.
 +                        </p>
 +                    </div>
 +                </div>
 +            </div>
 +        </section>
 +    </div>
  </div>
  
  <div id="snippet_feature" class="tab-pane">
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_image_gallery.png"/>
 -            <span class="oe_snippet_thumbnail_title">Image Gallery</span>
 -        </div>
 -        <section class="oe_snippet_body">
 +    <div name="Image Gallery" class="o_block_image_gallery">
 +        <section class="o_gallery o_nomode o_spc-none">
              <div class="container">
                  <div class="row">
 -                    <div class="col-md-12 text-center mt16 mb32">
 -                        <h2>Our Customer References</h2>
 -                        <h4 class="text-muted">More than 500 successful projects</h4>
 -                    </div>
                      <div class="col-md-6">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/desert.jpg"/>
                      </div>
                      <div class="col-md-3">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/china_thumb.jpg"/>
                      </div>
                      <div class="col-md-3">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/deers_thumb.jpg"/>
                      </div>
                      <div class="col-md-3">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/desert_thumb.jpg"/>
                      </div>
                      <div class="col-md-3">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/china_thumb.jpg"/>
                      </div>
                      <div class="col-md-3">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/deers_thumb.jpg"/>
                      </div>
                      <div class="col-md-6">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/landscape.jpg"/>
                      </div>
                      <div class="col-md-3">
 -                        <img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
 +                        <img class="img img-thumbnail img-responsive mb8 mt8" src="/website/static/src/img/china_thumb.jpg"/>
                      </div>
                  </div>
              </div>
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_comparison.png"/>
 -            <span class="oe_snippet_thumbnail_title">Comparisons</span>
 -        </div>
 -        <section class="oe_snippet_body">
 +    <div name="Comparisons" class="o_block_comparison">
 +        <section>
              <div class="container">
                <div class="row">
                  <div class="col-md-12 text-center mt16 mb32">
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_button.png"/>
 -            <span class="oe_snippet_thumbnail_title">Button</span>
 -        </div>
 -        <section class="oe_snippet_body jumbotron">
 +    <div name="Button" class="o_block_button">
 +        <section class="jumbotron">
              <div class="container">
                  <div class="row">
                      <div class="col-md-9 text-muted">
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_faq.png"/>
 -            <span class="oe_snippet_thumbnail_title">FAQ</span>
 -        </div>
 -        <section class="oe_snippet_body">
 +    <div name="FAQ" class="o_block_faq">
 +        <section>
              <div class="container">
                  <h2 class="page-header">
                      Point of Sale Questions <small>v7</small>
      </div>
  
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_references.png"/>
 -            <span class="oe_snippet_thumbnail_title">References</span>
 -        </div>
 -        <section class="oe_snippet_body mb32 mt16">
 +    <div name="References" class="o_block_references">
 +        <section class="mb32 mt16">
              <div class="container">
                  <div class="row">
                      <div class="col-md-12">
          </section>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_quotes_slider.png"/>
 -            <span class="oe_snippet_thumbnail_title">Quotes Slider</span>
 -        </div>
 -        <div id="myQuoteCarousel" class="oe_snippet_body carousel quotecarousel slide mb0">
 +    <div name="Quotes Slider" class="o_block_quotes_slider">
 +        <div id="myQuoteCarousel" class="carousel quotecarousel slide mb0" data-interval="10000">
              <!-- Indicators -->
              <ol class="carousel-indicators mb0">
                  <li data-target="#myQuoteCarousel" data-slide-to="0" class="active"></li>
          </div>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_features.png"/>
 -            <span class="oe_snippet_thumbnail_title">Feature Grid</span>
 -        </div>
 -        <section class="oe_snippet_body mb16">
 +    <div name="Feature Grid" class="o_block_features">
 +        <section class="mb16">
              <div class="container">
                  <div class="row">
                      <div class="col-md-5 text-center">
  
  <div id="snippet_effect" class="tab-pane">
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_parallax.png"/>
 -            <span class="oe_snippet_thumbnail_title">Parallax</span>
 -        </div>
 -        <section class="oe_snippet_body parallax"
 +    <div name="Parallax" class="o_block_parallax">
 +        <section class="parallax"
                  style="background-image: url('/website/static/src/img/banner/mountains.jpg')"
                  data-scroll-background-ratio="0.3">
                  <div><div class="oe_structure"/></div>
          </section>
      </div>
  
 -    <div>
 -        <div class="oe_snippet_thumbnail">
 -            <img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_quotes_slider.png"/>
 -            <span class="oe_snippet_thumbnail_title">Parallax Slider</span>
 -        </div>
 -        <section class="oe_snippet_body parallax"
 +    <div name="Parallax Slider" class="o_block_quotes_slider">
 +        <section class="parallax"
                   style="background-image: url('/website/static/src/img/parallax/quote.png')"
                  data-scroll-background-ratio="0.3">
              <div><div><div class="oe_structure">
 -                    <div id="myQuoteCarousel" class="carousel quotecarousel slide mb0">
 +                    <div id="myQuoteCarousel" class="carousel quotecarousel slide mb0" data-interval="10000">
                          <!-- Indicators -->
                          <ol class="carousel-indicators mb0">
                              <li data-target="#myQuoteCarousel" data-slide-to="0" class="active"></li>
  
  <template id="snippet_options">
  
 -    <div data-snippet-option-id='blog-style'
 -        data-selector="section:not(.carousel):not(.parallax)">
 +    <div data-js='blog-style'
 +        data-selector="section:not(.carousel):not(.parallax):not(.o_gallery)">
          <li class="dropdown-submenu">
              <a tabindex="-1" href="#">Style</a>
              <ul class="dropdown-menu">
 -                <li data-value="para_large"><a>Bigger Text</a></li>
 -                <li data-value="readable"><a>Narrow</a></li>
 +                <li data-toggle_class="para_large"><a>Bigger Text</a></li>
 +                <li data-toggle_class="readable"><a>Narrow</a></li>
              </ul>
          </li>
      </div>
  
 -    <div data-snippet-option-id='background'
 -        data-selector="section, .carousel, .parallax">
 -        <li class="dropdown-submenu" data-required="true">
 +    <div data-js='background'
 +        data-selector="section, :not(.o_gallery > .container) > .carousel, .parallax">
 +        <li class="dropdown-submenu" data-background="">
              <a tabindex="-1" href="#">Background</a>
              <ul class="dropdown-menu">
 -                <li class="dropdown-submenu">
 -                    <a tabindex="-2" href="#">Uniform Color</a>
 -                    <ul class="dropdown-menu">
 -                        <li data-value='oe_dark'><a>Darken</a></li>
 -                        <li data-value='oe_green'><a>Green</a></li>
 -                        <li data-value='oe_red'><a>Red</a></li>
 -                        <li data-value='oe_blue_light'><a>Turquoise</a></li>
 -                        <li data-value='oe_blue'><a>Dark Blue</a></li>
 -                        <li data-value='oe_orange'><a>Orange</a></li>
 -                        <li data-value='oe_purple'><a>Purple</a></li>
 -                        <li data-value='oe_black'><a>Black</a></li>
 -                    </ul>
 -                </li>
 -                <li class="dropdown-submenu">
 -                    <a tabindex="-2" href="#">People</a>
 -                    <ul class="dropdown-menu">
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/parallax/parallax_bg.jpg"><a>Sunflower</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/business_guy.jpg"><a>Business Guy</a></li>
 -                    </ul>
 -                </li>
 -                <li class="dropdown-submenu">
 -                    <a tabindex="-2" href="#">Landscape</a>
 -                    <ul class="dropdown-menu">
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/flower_field.jpg"><a>Flowers Field</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/landscape.jpg"><a>Landscape</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/mountains.jpg"><a>Mountains</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/greenfields.jpg"><a>Greenfields</a></li>
 -                    </ul>
 -                </li>
 -                <li class="dropdown-submenu">
 -                    <a tabindex="-2" href="#">Various</a>
 -                    <ul class="dropdown-menu">
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/aqua.jpg"><a>Aqua</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/baby_blue.jpg"><a>Baby Blue</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/black.jpg"><a>Black</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/color_splash.jpg"><a>Color Splash</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/mango.jpg"><a>Mango</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/orange_red.jpg"><a>Orange Red</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/flower.jpg"><a>Purple</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/velour.jpg"><a>Velour</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/wood.jpg"><a>Wood</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/banner/yellow_green.jpg"><a>Yellow Green</a></li>
 -                        <li data-value="oe_img_bg" data-src="/website/static/src/img/parallax/quote.png"><a>Quote</a></li>
 -                    </ul>
 -                </li>
 -                <li data-value=""><a>None</a></li>
 +                <li data-background="/website/static/src/img/parallax/parallax_bg.jpg"><a>Sunflower</a></li>
 +                <li data-background="/website/static/src/img/banner/business_guy.jpg"><a>Business Guy</a></li>
 +                <li data-background="/website/static/src/img/banner/flower_field.jpg"><a>Flowers Field</a></li>
 +                <li data-background="/website/static/src/img/banner/landscape.jpg"><a>Landscape</a></li>
 +                <li data-background="/website/static/src/img/banner/mountains.jpg"><a>Mountains</a></li>
 +                <li data-background="/website/static/src/img/banner/greenfields.jpg"><a>Greenfields</a></li>
 +                <li data-background="/website/static/src/img/banner/aqua.jpg"><a>Aqua</a></li>
 +                <li data-background="/website/static/src/img/banner/baby_blue.jpg"><a>Baby Blue</a></li>
 +                <li data-background="/website/static/src/img/banner/black.jpg"><a>Black</a></li>
 +                <li data-background="/website/static/src/img/banner/color_splash.jpg"><a>Color Splash</a></li>
 +                <li data-background="/website/static/src/img/banner/mango.jpg"><a>Mango</a></li>
 +                <li data-background="/website/static/src/img/banner/orange_red.jpg"><a>Orange Red</a></li>
 +                <li data-background="/website/static/src/img/banner/flower.jpg"><a>Purple</a></li>
 +                <li data-background="/website/static/src/img/banner/velour.jpg"><a>Velour</a></li>
 +                <li data-background="/website/static/src/img/banner/wood.jpg"><a>Wood</a></li>
 +                <li data-background="/website/static/src/img/banner/yellow_green.jpg"><a>Yellow Green</a></li>
 +                <li data-background="/website/static/src/img/parallax/quote.png"><a>Quote</a></li>
 +                <li data-background=""><a>None</a></li>
                  <li><a style="background: none; padding: 5px; border-top: 1px solid #ddd;"></a></li>
 -                <li class="oe_custom_bg" data-value="oe_img_bg"><a><b>Choose an image...</b></a></li>
 +                <li data-choose_image="choose_image" data-background=""><a><b>Choose an image...</b></a></li>
              </ul>
          </li>
      </div>
 +     
 +    <div data-js='gallery' data-selector=".o_gallery">
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Mode</a>
 +            
 +            <ul class="dropdown-menu">
 +                <li data-mode="nomode"   > <a>None</a></li>
 +                <li data-mode="masonry"  > <a>Masonry</a> </li>
 +                <li data-mode="grid"     > <a>Grid</a> </li>
 +                <li data-mode="slideshow"> <a>Slideshow</a> </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Slideshow speed</a>
 +            <ul class="dropdown-menu">
 +                 <li data-interval="1000"> <a>1s</a> </li>
 +                 <li data-interval="2000"> <a>2s</a> </li>
 +                 <li data-interval="3000"> <a>3s</a> </li>
 +                 <li data-interval="5000"> <a>5s</a> </li>
 +                 <li data-interval="10000"> <a>10s</a></li>
 +                 <li data-interval="0"   > <a>Disable autoplay</a> </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Columns</a>
 +            
 +            <ul class="dropdown-menu">
 +                <li data-columns="1"  > <a>1</a> </li>
 +                <li data-columns="2"  > <a>2</a> </li>
 +                <li data-columns="3"  > <a>3</a> </li>
 +                <li data-columns="4"  > <a>4</a> </li>
 +                <li data-columns="6"  > <a>6</a> </li>
 +                <li data-columns="12" > <a>12</a> </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Images spacing</a>
  
 -    <div data-snippet-option-id='carousel'
 -        data-selector=".carousel">
 -        <li class="divider"></li>
 -        <li>
 -            <a href="#" class="button js_add">Add Slide</a>
 +            <ul class="dropdown-menu">
 +                <li data-select_class="o_spc-none"  > <a>None</a>   </li>
 +                <li data-select_class="o_spc-small" > <a>Small</a>  </li>
 +                <li data-select_class="o_spc-medium"> <a>Medium</a> </li>
 +                <li data-select_class="o_spc-big"   > <a>Big</a>    </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +           <a tabindex="-2" href="#">Album</a>
 +           
 +           <ul class="dropdown-menu">
 +               <li data-albumimages="images_add"> <a>Add images</a></li>
 +               <li data-albumimages="images_rm"><a>Remove all images</a></li>
 +           </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +           <a tabindex="-2" href="#">Styling</a>
 +           
 +           <ul class="dropdown-menu">
 +               <li data-styling="img-rounded"   ><a>Rounded corners</a></li>
 +               <li data-styling="img-thumbnail" ><a>Thumbnails</a></li>
 +               <li data-styling="img-circle"    ><a>Circle</a></li>
 +               <li data-styling="shadow"        ><a>Shadows</a></li>
 +               <li data-styling="fa-spin"       ><a>Spinning</a></li>
 +           </ul>
 +        </li>
 +    </div>
 +
 +    <div data-js='colorpicker'
 +        data-selector="section, :not(.o_gallery > .container) > .carousel, .parallax">
 +        <li class="dropdown-submenu">
 +            <a tabindex="-1" href="#">Color</a>
 +            <ul class="dropdown-menu">
 +                <li></li>
 +            </ul>
          </li>
 -        <li>
 -            <a href="#" class="button js_remove">Remove Slide</a>
 +    </div>
 +     
 +    <div data-js='gallery' data-selector=".gallery">
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Mode</a>
 +            
 +            <ul class="dropdown-menu">
 +                <li data-mode="nomode"   > <a>None</a></li>
 +                <li data-mode="masonry"  > <a>Masonry</a> </li>
 +                <li data-mode="grid"     > <a>Grid</a> </li>
 +                <li data-mode="slideshow"> <a>Slideshow</a> </li>
 +            </ul>
          </li>
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Slideshow speed</a>
 +            <ul class="dropdown-menu">
 +                 <li data-interval="1000"> <a>1s</a> </li>
 +                 <li data-interval="2000"> <a>2s</a> </li>
 +                 <li data-interval="3000"> <a>3s</a> </li>
 +                 <li data-interval="5000"> <a>5s</a> </li>
 +                 <li data-interval="10000"> <a>10s</a></li>
 +                 <li data-interval="0"   > <a>Disable autoplay</a> </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Columns</a>
 +            
 +            <ul class="dropdown-menu">
 +                <li data-columns="1"  > <a>1</a> </li>
 +                <li data-columns="2"  > <a>2</a> </li>
 +                <li data-columns="3"  > <a>3</a> </li>
 +                <li data-columns="4"  > <a>4</a> </li>
 +                <li data-columns="6"  > <a>6</a> </li>
 +                <li data-columns="12" > <a>12</a> </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Images spacing</a>
 +
 +            <ul class="dropdown-menu">
 +                <li data-select_class="spc-none"  > <a>None</a>   </li>
 +                <li data-select_class="spc-small" > <a>Small</a>  </li>
 +                <li data-select_class="spc-medium"> <a>Medium</a> </li>
 +                <li data-select_class="spc-big"   > <a>Big</a>    </li>
 +            </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +           <a tabindex="-2" href="#">Album</a>
 +           
 +           <ul class="dropdown-menu">
 +               <li data-albumimages="images_add"> <a>Add images</a></li>
 +               <li data-albumimages="images_rm"><a>Remove all images</a></li>
 +           </ul>
 +        </li>
 +        <li class="dropdown-submenu">
 +           <a tabindex="-2" href="#">Styling</a>
 +           
 +           <ul class="dropdown-menu">
 +               <li data-styling="img-rounded"   ><a>Rounded corners</a></li>
 +               <li data-styling="img-thumbnail" ><a>Thumbnails</a></li>
 +               <li data-styling="img-circle"    ><a>Circle</a></li>
 +               <li data-styling="shadow"        ><a>Shadows</a></li>
 +               <li data-styling="fa-spin"       ><a>Spinning</a></li>
 +           </ul>
 +        </li>
 +    </div>
 +
 +    <div data-js='carousel'
 +        data-selector=":not(.o_gallery > .container) > .carousel">
 +        <li class="dropdown-submenu">
 +            <a tabindex="-2" href="#">Slideshow speed</a>
 +            <ul class="dropdown-menu">
 +                 <li data-interval="1000"> <a>1s</a> </li>
 +                 <li data-interval="2000"> <a>2s</a> </li>
 +                 <li data-interval="3000"> <a>3s</a> </li>
 +                 <li data-interval="5000"> <a>5s</a> </li>
 +                 <li data-interval="10000"> <a>10s</a></li>
 +                 <li data-interval="0"   > <a>Disable autoplay</a> </li>
 +            </ul>
 +        </li>
 +        <li class="divider"></li>
 +        <li data-add_slide="true"> <a href="#">Add Slide</a> </li>
 +        <li data-remove_slide="true"> <a href="#" >Remove Slide</a></li>
      </div>
  
 -    <div data-snippet-option-id='margin-y'
 -        data-selector="section, .row > [class*='col-md-'], .carousel, .parallax, hr">
 +    <div data-js='margin-y'
 +        data-selector="section, .row > [class*='col-md-'], :not(.o_gallery > .container) > .carousel, .parallax, hr">
      </div>
  
 -    <div data-snippet-option-id='resize'
 -        data-selector="section, .carousel, .parallax"
 -        data-selector-children=".oe_structure, [data-oe-type=html]">
 +    <div data-js='resize'
 +        data-selector="section, :not(.o_gallery > .container) > .carousel, .parallax"
 +        data-drop-in=".oe_structure, [data-oe-type=html]">
      </div>
  
 -    <div data-snippet-option-id='margin-x'
 +    <div data-js='margin-x'
          data-selector=".row > [class*='col-md-']"
 -        data-selector-vertical-children='.row'>
 +        data-drop-near=".row > [class*='col-md-']">
      </div>
  
 -    <div data-snippet-option-id='content'
 +    <div data-js='content'
-         data-selector="blockquote, .well, .panel, .oe_share"
+         data-selector="blockquote, .well, .panel, .oe_share, .o_image_floating"
 -        data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel, .oe_share"
 -        data-selector-children=".content">
 +        data-drop-near="p, h1, h2, h3, blockquote, .well, .panel, .oe_share"
 +        data-drop-in=".content">
      </div>
  
 -    <div data-snippet-option-id='separator'
 +    <div data-js='separator'
          data-selector="hr"
 -        data-selector-children=".oe_structure, [data-oe-type=html]">
 +        data-drop-in=".oe_structure, [data-oe-type=html]">
      </div>
  
+     <div data-snippet-option-id='image_floating_margin'
+         data-selector=".o_image_floating">
+         <li class="dropdown-submenu">
+             <a tabindex="-2" href="#">Margin</a>
+             <ul class="dropdown-menu">
+                 <li data-value="o_margin_xl"> <a>Extra-Large</a> </li>
+                 <li data-value="o_margin_l"> <a>Large</a> </li>
+                 <li data-value="o_margin_m"  > <a>Medium</a> </li>
+                 <li data-value="o_margin_s"  > <a>Small</a></li>
+                 <li data-value=""  > <a>None</a></li>
+             </ul>
+         </li>
+     </div>
+     <div data-snippet-option-id='image_floating_hidelink'
+         data-selector=".o_image_floating">
+         <li data-value="o_hide_link"><a>Hide link</a></li>
+     </div>
+     <div data-snippet-option-id='image_floating_side'
+          data-selector=".o_image_floating">
+         <li class="dropdown-submenu">
+             <a tabindex="-2" href="#">Float</a>
+             <ul class="dropdown-menu">
+                 <li data-value="pull-left"><a>Left</a></li>
+                 <li data-value="pull-right"><a>Right</a></li>
+             </ul>
+         </li>
+     </div>
 -    <div data-snippet-option-id='parallax'
 +    <div data-js='parallax'
          data-selector=".parallax">
          <li class="dropdown-submenu">
              <a tabindex="-1" href="#">Scroll Speed</a>
              <ul class="dropdown-menu" name="parallax-scroll">
 -                <li data-value="0"><a>Static</a></li>
 -                <li data-value="0.3"><a>Very Slow</a></li>
 -                <li data-value="0.6"><a>Slow</a></li>
 -                <li data-value="1"><a>Fixed</a></li>
 -                <li data-value="1.4"><a>Fast</a></li>
 -                <li data-value="1.7"><a>Very Fast</a></li>
 +                <li data-scroll="0"><a>Static</a></li>
 +                <li data-scroll="0.3"><a>Very Slow</a></li>
 +                <li data-scroll="0.6"><a>Slow</a></li>
 +                <li data-scroll="1"><a>Fixed</a></li>
 +                <li data-scroll="1.4"><a>Fast</a></li>
 +                <li data-scroll="1.7"><a>Very Fast</a></li>
              </ul>
          </li>
      </div>
  
 -    <div data-snippet-option-id='media'
 +    <div data-js='media'
          data-selector="img:not(.cke_iframe), .media_iframe_video, span.fa, i.fa, .glyphicon">
 -        <li><a href="#" class="edition">Change...</a></li>
 +        <li data-edition="edition"><a>Change...</a></li>
      </div>
  
 -    <div data-snippet-option-id='transform'
 +    <div data-js='transform'
          data-selector="img:not(.cke_iframe), .media_iframe_video, span.fa, i.fa">
          <li class="dropdown-submenu">
              <a tabindex="-1" href="#">Style</a>
              <ul class="dropdown-menu" name="parallax-scroll">
 -                <li data-value="img-rounded"><a>Rounded corners</a></li>
 -                <li data-value="img-thumbnail"><a>Box</a></li>
 -                <li data-value="img-circle"><a>Circle</a></li>
 -                <li data-value="shadow"><a>Shadow</a></li>
 -                <li data-value="fa-spin"><a>Spin</a></li>
 +                <li data-select_class="img-rounded"><a>Rounded corners</a></li>
 +                <li data-select_class="img-thumbnail"><a>Box</a></li>
 +                <li data-select_class="img-circle"><a>Circle</a></li>
 +                <li data-select_class="shadow"><a>Shadow</a></li>
 +                <li data-select_class="fa-spin"><a>Spin</a></li>
              </ul>
          </li>
 -        <li><a href="#" class="style">Transform</a></li>
 -        <li><a href="#" class="clear-style">Reset Transformation</a></li>
 +        <li data-style=""><a>Transform</a></li>
 +        <li data-clear_style=""><a>Reset Transformation</a></li>
      </div>
  
  </template>
@@@ -10,7 -10,7 +10,7 @@@
              <field name="website_published">True</field>
              <field name="twitter_hashtag">openerp</field>
              <field name="description"><![CDATA[
 -<div class="carousel slide oe_small mb16" data-interval="10000" data-snippet-id="carousel" id="myCarousel3">
 +<div class="carousel slide oe_small mb16" data-interval="10000" id="myCarousel3">
  <div class="carousel-inner">
  <div class="item active image_text"><img height="100%" src="/website/static/src/img/banner/orange_red.jpg" width="100%"><div class="container">
  <div class="carousel-caption content">
@@@ -18,7 -18,7 +18,7 @@@
  
  <h4>to learn .JS development</h4>
  </div>
- <img class="carousel-image hidden-xs" alt="Banner Odoo Image" src="/website/static/src/img/banner/banner_picture.png">
+ <img class="hidden-xs" alt="Banner Odoo Image" src="/website/static/src/img/banner/banner_picture.png">
  </div>
  </div>
  </div>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mb8 mt0" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mb8 mt0">
  <h2>Course Summary</h2>
  </div>
  
 -<div class="col-md-12 mt16 mb0" data-snippet-id="colmd">
 +<div class="col-md-12 mt16 mb0">
  <p><span style="text-align: -webkit-center; ">This course is dedicated to&nbsp;developers who need to grasp knowledge of the <strong>business applications development </strong>process. This course is for new developers or for IT professionals eager to learn more about technical aspects.</span></p>
  </div>
  </div>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mt0 mb16" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mt0 mb16">
  <h2>What you will learn?</h2>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 1</h2>
@@@ -62,7 -62,7 +62,7 @@@
  </div>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 2</h2>
@@@ -83,7 -83,7 +83,7 @@@
  </div>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 3</h2>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mb16 mt0" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mb16 mt0">
  <h2>Requirements</h2>
  </div>
  
 -<div class="col-md-12 mb16 mt16" data-snippet-id="colmd">
 +<div class="col-md-12 mb16 mt16">
  <p><strong>Objectives:</strong></p>
  
  <p><strong>Having attended this course, participants should be able to:</strong></p>
  
  <div class="container oe_dark">
  <div class="row">
 -<div class="col-md-12" data-snippet-id="colmd">
 +<div class="col-md-12">
  <h1 class="text-center">Read Great Contents</h1>
  
  <h3 class="text-muted text-center">What do people&nbsp;say about this course?</h3>
  </div>
  
 -<div class="mt16 mb32 col-md-6" data-snippet-id="colmd">
 -<blockquote data-snippet-id="quote">
 +<div class="mt16 mb32 col-md-6">
 +<blockquote>
  <p>I did not expect such a great learning experience. I feel like I can develop anything now.</p>
  <small>John Doe, CEO</small></blockquote>
  </div>
  
 -<div class="mt16 mb32 col-md-6" data-snippet-id="colmd">
 -<blockquote data-snippet-id="quote">
 +<div class="mt16 mb32 col-md-6">
 +<blockquote>
  <p>This course help me build my first application within a month. Definetly worth its price.</p>
  <small>John Doe, CEO</small></blockquote>
  </div>
              <field name="website_published">True</field>
              <field name="twitter_hashtag">openerp</field>
              <field name="description"><![CDATA[
 -<div class="carousel slide oe_small mb16" data-interval="10000" data-snippet-id="carousel" id="myCarousel3">
 +<div class="carousel slide oe_small mb16" data-interval="10000" id="myCarousel3">
  <div class="carousel-inner">
  <div class="item active image_text"><img height="100%" src="/website/static/src/img/banner/orange_red.jpg" width="100%"><div class="container">
  <div class="carousel-caption content">
  
  <h4>to learn .JS development</h4>
  </div>
- <img class="carousel-image hidden-xs" alt="Banner Odoo Image" src="/website/static/src/img/banner/banner_picture.png">
+ <img class="hidden-xs" alt="Banner Odoo Image" src="/website/static/src/img/banner/banner_picture.png">
  </div>
  </div>
  </div>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mb8 mt0" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mb8 mt0">
  <h2>Course Summary</h2>
  </div>
  
 -<div class="col-md-12 mt16 mb0" data-snippet-id="colmd">
 +<div class="col-md-12 mt16 mb0">
  <p><span style="text-align: -webkit-center; ">This course is dedicated to&nbsp;developers who need to grasp knowledge of the <strong>business applications development </strong>process. This course is for new developers or for IT professionals eager to learn more about technical aspects.</span></p>
  </div>
  </div>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mt0 mb16" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mt0 mb16">
  <h2>What you will learn?</h2>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 1</h2>
  </div>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 2</h2>
  </div>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 3</h2>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mb16 mt0" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mb16 mt0">
  <h2>Requirements</h2>
  </div>
  
 -<div class="col-md-12 mb16 mt16" data-snippet-id="colmd">
 +<div class="col-md-12 mb16 mt16">
  <p><strong>Objectives:</strong></p>
  
  <p><strong>Having attended this course, participants should be able to:</strong></p>
  
  <div class="container oe_dark">
  <div class="row">
 -<div class="col-md-12" data-snippet-id="colmd">
 +<div class="col-md-12">
  <h1 class="text-center">Read Great Contents</h1>
  
  <h3 class="text-muted text-center">What do people&nbsp;say about this course?</h3>
  </div>
  
 -<div class="mt16 mb32 col-md-6" data-snippet-id="colmd">
 -<blockquote data-snippet-id="quote">
 +<div class="mt16 mb32 col-md-6">
 +<blockquote>
  <p>I did not expect such a great learning experience. I feel like I can develop anything now.</p>
  <small>John Doe, CEO</small></blockquote>
  </div>
  
 -<div class="mt16 mb32 col-md-6" data-snippet-id="colmd">
 -<blockquote data-snippet-id="quote">
 +<div class="mt16 mb32 col-md-6">
 +<blockquote>
  <p>This course help me build my first application within a month. Definetly worth its price.</p>
  <small>John Doe, CEO</small></blockquote>
  </div>
              <field name="website_published">True</field>
              <field name="twitter_hashtag">openerp</field>
              <field name="description"><![CDATA[
 -<div class="carousel slide oe_small mb16" data-interval="10000" data-snippet-id="carousel" id="myCarousel3">
 +<div class="carousel slide oe_small mb16" data-interval="10000" id="myCarousel3">
  <div class="carousel-inner">
  <div class="item active image_text"><img height="100%" src="/website/static/src/img/banner/orange_red.jpg" width="100%"><div class="container">
  <div class="carousel-caption content">
  <h4>to learn .JS development</h4>
  </div>
  
- <img class="carousel-image hidden-xs" alt="Banner Odoo Image" src="/website/static/src/img/banner/banner_picture.png">
+ <img class="hidden-xs" alt="Banner Odoo Image" src="/website/static/src/img/banner/banner_picture.png">
  </div>
  </div>
  </div>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mb8 mt0" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mb8 mt0">
  <h2>Course Summary</h2>
  </div>
  
 -<div class="col-md-12 mt16 mb0" data-snippet-id="colmd">
 +<div class="col-md-12 mt16 mb0">
  <p><span style="text-align: -webkit-center; ">This course is dedicated to&nbsp;developers who need to grasp knowledge of the <strong>business applications development </strong>process. This course is for new developers or for IT professionals eager to learn more about technical aspects.</span></p>
  </div>
  </div>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mt0 mb16" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mt0 mb16">
  <h2>What you will learn?</h2>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 1</h2>
  </div>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 2</h2>
  </div>
  </div>
  
 -<div class="col-md-4" data-snippet-id="colmd">
 +<div class="col-md-4">
  <div class="panel panel-info">
  <div class="panel-heading text-center">
  <h2 style="margin: 0">Day 3</h2>
  
  <div class="container">
  <div class="row">
 -<div class="col-md-12 text-center mb16 mt0" data-snippet-id="colmd">
 +<div class="col-md-12 text-center mb16 mt0">
  <h2>Requirements</h2>
  </div>
  
 -<div class="col-md-12 mb16 mt16" data-snippet-id="colmd">
 +<div class="col-md-12 mb16 mt16">
  <p><strong>Objectives:</strong></p>
  
  <p><strong>Having attended this course, participants should be able to:</strong></p>
  
  <div class="container oe_dark">
  <div class="row">
 -<div class="col-md-12" data-snippet-id="colmd">
 +<div class="col-md-12">
  <h1 class="text-center">Read Great Contents</h1>
  
  <h3 class="text-muted text-center">What do people&nbsp;say about this course?</h3>
  </div>
  
 -<div class="mt16 mb32 col-md-6" data-snippet-id="colmd">
 -<blockquote data-snippet-id="quote">
 +<div class="mt16 mb32 col-md-6">
 +<blockquote>
  <p>I did not expect such a great learning experience. I feel like I can develop anything now.</p>
  <small>John Doe, CEO</small></blockquote>
  </div>
  
 -<div class="mt16 mb32 col-md-6" data-snippet-id="colmd">
 -<blockquote data-snippet-id="quote">
 +<div class="mt16 mb32 col-md-6">
 +<blockquote>
  <p>This course help me build my first application within a month. Definetly worth its price.</p>
  <small>John Doe, CEO</small></blockquote>
  </div>
  # -*- coding: utf-8 -*-
 -##############################################################################
 -#
 -#    OpenERP, Open Source Management Solution
 -#    Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
 -#
 -#    This program is free software: you can redistribute it and/or modify
 -#    it under the terms of the GNU Affero General Public License as
 -#    published by the Free Software Foundation, either version 3 of the
 -#    License, or (at your option) any later version.
 -#
 -#    This program is distributed in the hope that it will be useful,
 -#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 -#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 -#    GNU Affero General Public License for more details.
 -#
 -#    You should have received a copy of the GNU Affero General Public License
 -#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -#
 -##############################################################################
  
 -from openerp.osv import osv, fields
 +from openerp import models, fields, api, _
 +
 +# from openerp.osv import osv, fields
  from openerp import SUPERUSER_ID
 +from openerp.models import NewId
  
 -from openerp.tools.translate import _
 +# from openerp.tools.translate import _
  import re
  
  from openerp.addons.website.models.website import slug
  
  
 -class event(osv.osv):
 +class event(models.Model):
      _name = 'event.event'
 -    _inherit = ['event.event','website.seo.metadata']
 +    _inherit = ['event.event', 'website.seo.metadata']
+     _track = {
+         'website_published': {
+             'website_event.mt_event_published': lambda self, cr, uid, obj, ctx=None: obj.website_published,
+             'website_event.mt_event_unpublished': lambda self, cr, uid, obj, ctx=None: not obj.website_published
+         },
+     }
  
 -    def _get_new_menu_pages(self, cr, uid, event, context=None):
 -        context = context or {}
 +    twitter_hashtag = fields.Char('Twitter Hashtag', default=lambda self: self._default_hashtag())
 +    website_published = fields.Boolean('Visible in Website', copy=False)
 +    # TDE TODO FIXME: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
 +    website_message_ids = fields.One2many(
 +        'mail.message', 'res_id',
 +        domain=lambda self: [
 +            '&', ('model', '=', self._name), ('type', '=', 'comment')
 +        ],
 +        string='Website Messages',
 +        help="Website communication history",
 +    )
 +    website_url = fields.Char('Website url', compute='_website_url')
 +
 +    @api.one
 +    @api.depends('name')
 +    def _website_url(self):
 +        if isinstance(self.id, NewId):
 +            self.website_url = ''
 +        else:
 +            self.website_url = "/event/" + slug(self)
 +
 +    def _default_hashtag(self):
 +        return re.sub("[- \\.\\(\\)\\@\\#\\&]+", "", self.env.user.company_id.name).lower()
 +
 +    show_menu = fields.Boolean('Has Dedicated Menu', compute='_get_show_menu', inverse='_set_show_menu')
 +    menu_id = fields.Many2one('website.menu', 'Event Menu')
 +
 +    @api.one
 +    def _get_new_menu_pages(self):
          todo = [
              (_('Introduction'), 'website_event.template_intro'),
              (_('Location'), 'website_event.template_location')
          ]
 -        web = self.pool.get('website')
          result = []
 -        for name,path in todo:
 -            name2 = name+' '+event.name
 -            newpath = web.new_page(cr, uid, name2, path, ispage=False, context=context)
 -            url = "/event/"+slug(event)+"/page/" + newpath
 +        for name, path in todo:
 +            complete_name = name + ' ' + self.name
 +            newpath = self.env['website'].new_page(complete_name, path, ispage=False)
 +            url = "/event/" + slug(self) + "/page/" + newpath
              result.append((name, url))
 +        result.append((_('Register'), '/event/%s/register' % slug(self)))
          return result
  
 -    def _set_show_menu(self, cr, uid, ids, name, value, arg, context=None):
 -        menuobj = self.pool.get('website.menu')
 -        eventobj = self.pool.get('event.event')
 -        for event in self.browse(cr, uid, [ids], context=context):
 -            if event.menu_id and not value:
 -                menuobj.unlink(cr, uid, [event.menu_id.id], context=context)
 -            elif value and not event.menu_id:
 -                root = menuobj.create(cr, uid, {
 -                    'name': event.name
 -                }, context=context)
 -                tocreate = self._get_new_menu_pages(cr, uid, event, context)
 -                tocreate.append((_('Register'), '/event/%s/register' % slug(event)))
 -                sequence = 0
 -                for name,url in tocreate:
 -                    menuobj.create(cr, uid, {
 -                        'name': name,
 -                        'url': url,
 -                        'parent_id': root,
 -                        'sequence': sequence
 -                    }, context=context)
 -                    sequence += 1
 -                eventobj.write(cr, uid, [event.id], {'menu_id': root}, context=context)
 -        return True
 -
 -    def _get_show_menu(self, cr, uid, ids, field_name, arg, context=None):
 -        res = dict.fromkeys(ids, '')
 -        for event in self.browse(cr, uid, ids, context=context):
 -            res[event.id] = bool(event.menu_id)
 -        return res
 +    @api.one
 +    def _set_show_menu(self):
 +        if self.menu_id and not self.show_menu:
 +            self.menu_id.unlink()
 +        elif self.show_menu and not self.menu_id:
 +            root_menu = self.env['website.menu'].create({'name': self.name})
 +            to_create_menus = self._get_new_menu_pages()[0]  # TDE CHECK api.one -> returns a list with one item ?
 +            seq = 0
 +            for name, url in to_create_menus:
 +                self.env['website.menu'].create({
 +                    'name': name,
 +                    'url': url,
 +                    'parent_id': root_menu.id,
 +                    'sequence': seq,
 +                })
 +                seq += 1
 +            self.menu_id = root_menu
  
 -    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
 -        res = dict.fromkeys(ids, '')
 -        for event in self.browse(cr, uid, ids, context=context):
 -            res[event.id] = "/event/" + slug(event)
 -        return res
 -
 -    def _default_hashtag(self, cr, uid, context={}):
 -        name = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
 -        return re.sub("[- \\.\\(\\)\\@\\#\\&]+", "", name).lower()
 -
 -    _columns = {
 -        'twitter_hashtag': fields.char('Twitter Hashtag'),
 -        'website_published': fields.boolean('Visible in Website', copy=False),
 -        # TDE TODO FIXME: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
 -        'website_message_ids': fields.one2many(
 -            'mail.message', 'res_id',
 -            domain=lambda self: [
 -                '&', ('model', '=', self._name), ('type', '=', 'comment')
 -            ],
 -            string='Website Messages',
 -            help="Website communication history",
 -        ),
 -        'website_url': fields.function(_website_url, string="Website url", type="char"),
 -        'show_menu': fields.function(_get_show_menu, fnct_inv=_set_show_menu, type='boolean', string='Dedicated Menu'),
 -        'menu_id': fields.many2one('website.menu', 'Event Menu'),
 -    }
 -    _defaults = {
 -        'show_menu': False,
 -        'twitter_hashtag': _default_hashtag
 -    }
 +    @api.one
 +    def _get_show_menu(self):
 +        self.show_menu = bool(self.menu_id)
  
      def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
          event = self.browse(cr, uid, ids[0], context=context)
          if event.address_id:
              return self.browse(cr, SUPERUSER_ID, ids[0], context=context).address_id.google_map_link()
          return None
 -
@@@ -9,44 -9,28 +9,44 @@@
          steps: [
              {
                  title:     "select event",
 -                element:   'a[href*="/event"]:contains("Conference on Business Applications"):first',
 +                element:   'a[href*="/event"]:contains("Conference on Business Apps"):first',
              },
              {
 -                waitNot:   'a[href*="/event"]:contains("Conference on Business Applications")',
 -                title:     "select 2 Standard tickets",
 +                waitNot:   'a[href*="/event"]:contains("Conference on Business Apps")',
 +                title:     "select 1 Standard ticket",
                  element:   'select:eq(0)',
 -                sampleText: '2',
 +                sampleText: '1',
              },
              {
 -                title:     "select 3 VIP tickets",
 -                waitFor:   'select:eq(0) option:contains(2):selected',
 +                title:     "select 2 VIP tickets",
 +                waitFor:   'select:eq(0) option:contains(1):selected',
                  element:   'select:eq(1)',
 -                sampleText: '3',
 +                sampleText: '2',
              },
              {
                  title:     "Order Now",
 -                waitFor:   'select:eq(1) option:contains(3):selected',
 +                waitFor:   'select:eq(1) option:contains(2):selected',
                  element:   '.btn-primary:contains("Order Now")',
              },
              {
 +                title:     "Add the details of attendees",
 +                waitFor:   'form[id="attendee_registration"] .btn:contains("Continue")',
 +                autoComplete: function (tour) {
 +                    $("input[name='1-name']").val("Att1");
 +                    $("input[name='1-phone']").val("111 111");
 +                    $("input[name='1-email']").val("att1@example.com");
 +                    $("input[name='1-name']").val("Att2");
 +                    $("input[name='1-phone']").val("222 222");
 +                    $("input[name='1-email']").val("att2@example.com");
 +                },
 +            },
 +            {
 +                title:     "click in modal on 'Continue' button",
 +                element:   '.modal button:contains("Continue")',
 +            },
 +            {
                  title:     "Check the cart",
 -                element:   '#top_menu .my_cart_quantity:contains(5)'
 +                element:   '#top_menu .my_cart_quantity:contains(3)'
              },
              {
                  title:     "Check if the cart have 2 order lines and add one VIP ticket",
@@@ -55,7 -39,7 +55,7 @@@
              },
              {
                  title:     "Process Checkout",
 -                waitFor:   '#top_menu .my_cart_quantity:contains(6)',
 +                waitFor:   '#top_menu .my_cart_quantity:contains(4)',
                  element:   '.btn-primary:contains("Process Checkout")'
              },
              {
@@@ -67,7 -51,7 +67,7 @@@
                      if ($("input[name='email']").val() === "")
                          $("input[name='email']").val("website_event_sale_test_shoptest@websiteeventsaletest.odoo.com");
                      $("input[name='phone']").val("123");
-                     $("input[name='street']").val("123");
+                     $("input[name='street2']").val("123");
                      $("input[name='city']").val("123");
                      $("input[name='zip']").val("123");
                      $("select[name='country_id']").val("21");
@@@ -14,7 -14,7 +14,7 @@@
          <t t-set="additional_title">Members</t>
          <div id="wrap">
              <div class="oe_structure">
 -                <section data-snippet-id="title">
 +                <section>
                      <div class="container">
                          <div class="row">
                              <div class="col-md-12">
@@@ -89,7 -89,7 +89,7 @@@
          <ul class="nav nav-pills nav-stacked mt16">
              <li class="nav-header"><h3>Location</h3></li>
              <t t-foreach="countries" t-as="country">
-                 <li t-if="country['country_id']" t-att-class="country['country_id'][0] == current_country_id and 'active' or ''">
+                 <li t-if="country['country_id']" t-att-class="country['country_id'] and country['country_id'][0] == current_country_id and 'active' or ''">
                      <a t-attf-href="/members#{ membership and '/association/%s' % membership.id or '' }#{ country['country_id'][0] and '/country/%s' % slug(country['country_id']) or '' }#{ search }"><t t-esc="country['country_id'][1]"/>
                          <span class="badge pull-right"><t t-esc="country['country_id_count'] or '0'"/></span>
                      </a>
@@@ -1,15 -1,5 +1,15 @@@
  <openerp>
      <data>
 +        <!-- After installation of the module, open the quotations menu -->
 +        <record id="open_quotation_menu" model="ir.actions.client">
 +              <field name="name">Open Quotations</field>
 +              <field name="tag">reload</field>
 +              <field name="params" eval="{'menu_id': ref('sale.menu_sale_quotations')}"/>
 +        </record>
 +        <record id="base.open_menu" model="ir.actions.todo">
 +              <field name="action_id" ref="open_quotation_menu"/>
 +              <field name="state">open</field>
 +        </record>
  
          <!--
              Update Email template to send right quote url
              ]]></field>
          </record>
  
 +        <record id="website_quote_template_default" model="sale.quote.template">
 +            <field name="name">Default Template</field>
 +            <field name="number_of_days">30</field>
 +            <field name="website_description" type="xml">
 +                <section data-snippet-id="title">
 +                    <center><h1 class="page-header">Our offer</h1></center>
 +                </section>
 +                <section data-snippet-id="text-block">
 +                    <div class="row">
 +                        <div class="col-md-12">
 +                            <p>
 +                                <h3 class="text-muted">Our offer is based on 3 commitments, which drive our business and our partners:</h3>
 +                            </p>
 +                        </div>
 +                    </div>
 +                </section>
 +                <section data-snippet-id="quality">
 +                    <div class="row mt32">
 +                        <div class="col-md-4">
 +                            <div class="panel panel-info">
 +                                <div class="panel-heading">
 +                                   <center><h3 class="panel-title">Quality</h3></center>
 +                                </div>
 +                                <div class="panel-body">
 +                                   <p>
 +                                       Quality in many ways, our reputation was built on the first value.
 +                                   </p>
 +                                </div>
 +                            </div>
 +                        </div>
 +                        <div class="col-md-4">
 +                            <div class="panel panel-info">
 +                                <div class="panel-heading">
 +                                   <center><h3 class="panel-title">Service</h3></center>
 +                                </div>
 +                                <div class="panel-body">
 +                                   <p>
 +                                       We are always reachable to help you, do not hesitate to contact us at any time.
 +                                   </p>
 +                                </div>
 +                            </div>
 +                        </div>
 +                        <div class="col-md-4">
 +                            <div class="panel panel-info">
 +                                <div class="panel-heading">
 +                                   <center><h3 class="panel-title">Price</h3></center>
 +                                </div>
 +                                <div class="panel-body">
 +                                   <p>
 +                                     Our prices are the lowest in the region, if you find cheaper elsewhere, we will refund the difference 
 +                                   </p>
 +                                </div>
 +                            </div>
 +                        </div>
 +                    </div>
 +                </section>
 +                <section data-snippet-id="text-block">
 +                    <center><h2>Your products/services</h2></center>
 +                    <p>
 +                        <h3 class="text-muted">
 +                            The description of products/services from your quotation will be displayed below
 +                        </h3>
 +                    </p>
 +                    <p>
 +                           <b> To write a quotation description, edit your product, go the sales tab and add a text in "Description for quotations"</b>
 +                    </p>
 +                </section>
 +            </field>
 +        </record>
      </data>
+     <!-- Mail template is done in a NOUPDATE block
+          so users can freely customize/delete them -->
+     <data noupdate="1">
+         <!--Email template -->
+         <record id="email_template_edi_sale" model="email.template">
+             <field name="name">Sales Order - Send by Email (Online Quote)</field>
+             <field name="email_from">${(object.user_id.email or '')|safe}</field>
+             <field name="subject">${object.company_id.name|safe} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
+             <field name="partner_to">${object.partner_invoice_id.id}</field>
+             <field name="model_id" ref="sale.model_sale_order"/>
+             <field name="auto_delete" eval="True"/>
+             <field name="report_template" ref="sale.report_sale_order"/>
+             <field name="report_name">${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
+             <field name="lang">${object.partner_id.lang}</field>
+             <field name="user_signature" eval="True"/>
+             <field name="body_html"><![CDATA[
+ <div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
+     <p>Hello ${object.partner_id.name},</p>
+     <p>Here is your ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} from ${object.company_id.name}: </p>
+     <p style="border-left: 1px solid #8e0000; margin-left: 30px;">
+        &nbsp;&nbsp;<strong>REFERENCES</strong><br />
+        &nbsp;&nbsp;Order number: <strong>${object.name}</strong><br />
+        &nbsp;&nbsp;Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br />
+        &nbsp;&nbsp;Order date: ${object.date_order}<br />
+        % if object.origin:
+        &nbsp;&nbsp;Order reference: ${object.origin}<br />
+        % endif
+        % if object.client_order_ref:
+        &nbsp;&nbsp;Your reference: ${object.client_order_ref}<br />
+        % endif
+        % if object.user_id:
+        &nbsp;&nbsp;Your contact: <a href="mailto:${object.user_id.email or ''}?subject=Order%20${object.name}">${object.user_id.name}</a>
+        % endif
+     </p>
+     <% set signup_url = object.get_signup_url() %>
+     % if signup_url:
+     <p>
+     You can access this document and pay online via our Customer Portal:
+     </p>
+         <a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #DDD; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
+            href="${signup_url}">View ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'}</a>
+     % endif
+     % if object.paypal_url:
+     <br/>
+     <p>It is also possible to directly pay with Paypal:</p>
+         <a style="margin-left: 120px;" href="${object.paypal_url}">
+             <img class="oe_edi_paypal_button" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
+         </a>
+     % endif
+     <br/>
+     <p>If you have any question, do not hesitate to contact us.</p>
+     <p>Thank you for choosing ${object.company_id.name or 'us'}!</p>
+     <br/>
+     <br/>
+     <div style="width: 375px; margin: 0px; padding: 0px; background-color: #8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; background-repeat: repeat no-repeat;">
+         <h3 style="margin: 0px; padding: 2px 14px; font-size: 12px; color: #DDD;">
+             <strong style="text-transform:uppercase;">${object.company_id.name}</strong></h3>
+     </div>
+     <div style="width: 347px; margin: 0px; padding: 5px 14px; line-height: 16px; background-color: #F2F2F2;">
+         <span style="color: #222; margin-bottom: 5px; display: block; ">
+         % if object.company_id.street:
+             ${object.company_id.street}<br/>
+         % endif
+         % if object.company_id.street2:
+             ${object.company_id.street2}<br/>
+         % endif
+         % if object.company_id.city or object.company_id.zip:
+             ${object.company_id.zip} ${object.company_id.city}<br/>
+         % endif
+         % if object.company_id.country_id:
+             ${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}<br/>
+         % endif
+         </span>
+         % if object.company_id.phone:
+             <div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">
+                 Phone:&nbsp; ${object.company_id.phone}
+             </div>
+         % endif
+         % if object.company_id.website:
+             <div>
+                 Web :&nbsp;<a href="${object.company_id.website}">${object.company_id.website}</a>
+             </div>
+         % endif
+         <p></p>
+     </div>
+ </div>
+             ]]></field>
+         </record>
+     </data>
  </openerp>
@@@ -32,10 -32,10 +32,10 @@@ class sale_quote_template(osv.osv)
      _columns = {
          'name': fields.char('Quotation Template', required=True),
          'website_description': fields.html('Description', translate=True),
 -        'quote_line': fields.one2many('sale.quote.line', 'quote_id', 'Quote Template Lines', copy=True),
 +        'quote_line': fields.one2many('sale.quote.line', 'quote_id', 'Quotation Template Lines', copy=True),
          'note': fields.text('Terms and conditions'),
          'options': fields.one2many('sale.quote.option', 'template_id', 'Optional Products Lines', copy=True),
 -        'number_of_days': fields.integer('Quote Duration', help='Number of days for the validaty date computation of the quotation'),
 +        'number_of_days': fields.integer('Quotation Duration', help='Number of days for the validity date computation of the quotation'),
      }
      def open_template(self, cr, uid, quote_id, context=None):
          return {
@@@ -48,7 -48,6 +48,7 @@@ class sale_quote_line(osv.osv)
      _name = "sale.quote.line"
      _description = "Quotation Template Lines"
      _columns = {
 +        'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale quote lines."),
          'quote_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', select=True),
          'name': fields.text('Description', required=True, translate=True),
          'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
          'product_uom_qty': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
          'product_uom_id': fields.many2one('product.uom', 'Unit of Measure ', required=True),
      }
 +    _order = 'sequence, id'
      _defaults = {
          'product_uom_qty': 1,
          'discount': 0.0,
 +        'sequence': 10,
      }
      def on_change_product_id(self, cr, uid, ids, product, context=None):
          vals = {}
@@@ -140,29 -137,20 +140,29 @@@ class sale_order(osv.osv)
  
      _columns = {
          'access_token': fields.char('Security Token', required=True, copy=False),
 -        'template_id': fields.many2one('sale.quote.template', 'Quote Template', readonly=True,
 -            states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}),
 +        'template_id': fields.many2one('sale.quote.template', 'Quotation Template', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}),
          'website_description': fields.html('Description'),
 -        'options' : fields.one2many('sale.order.option', 'order_id', 'Optional Products Lines'),
 -        'validity_date': fields.date('Expiry Date'),
 +        'options' : fields.one2many('sale.order.option', 'order_id', 'Optional Products Lines', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, copy=True),
          'amount_undiscounted': fields.function(_get_total, string='Amount Before Discount', type="float",
 -            digits_compute=dp.get_precision('Account'))
 +            digits_compute=dp.get_precision('Account')),
 +        'quote_viewed': fields.boolean('Quotation Viewed')
      }
 +
 +    def _get_template_id(self, cr, uid, context=None):
 +        try:
 +            template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'website_quote', 'website_quote_template_default')[1]
 +        except ValueError:
 +            template_id = False
 +        return template_id
 +
      _defaults = {
 -        'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4())
 +        'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4()),
 +        'template_id' : _get_template_id,
      }
  
      def open_quotation(self, cr, uid, quote_id, context=None):
          quote = self.browse(cr, uid, quote_id[0], context=context)
 +        self.write(cr, uid, quote_id[0], {'quote_viewed': True}, context=context)
          return {
              'type': 'ir.actions.act_url',
              'target': 'self',
  
          if context is None:
              context = {}
 -        context = dict(context, lang=self.pool.get('res.partner').browse(cr, uid, partner, context).lang)
 -        
 +        if partner:
 +            context['lang'] = self.pool['res.partner'].browse(cr, uid, partner, context).lang
 +
          lines = [(5,)]
          quote_template = self.pool.get('sale.quote.template').browse(cr, uid, template_id, context=context)
          for line in quote_template.quote_line:
              'res_id': id,
          }
  
+     def action_quotation_send(self, cr, uid, ids, context=None):
+         action = super(sale_order, self).action_quotation_send(cr, uid, ids, context=context)
+         ir_model_data = self.pool.get('ir.model.data')
+         quote_template_id = self.read(cr, uid, ids, ['template_id'], context=context)[0]['template_id']
+         if quote_template_id:
+             try:
+                 template_id = ir_model_data.get_object_reference(cr, uid, 'website_quote', 'email_template_edi_sale')[1]
+             except ValueError:
+                 pass
+             else:
+                 action['context'].update({
+                     'default_template_id': template_id,
+                     'default_use_template': True
+                 })
+         return action
  
  class sale_quote_option(osv.osv):
      _name = "sale.quote.option"
 -    _description = "Quote Option"
 +    _description = "Quotation Option"
      _columns = {
          'template_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', ondelete='cascade', select=True, required=True),
          'name': fields.text('Description', required=True, translate=True),
@@@ -11,7 -11,7 +11,7 @@@
    </template>
  
    <template id="pricing" name="Price">
 -      <section data-snippet-id="title">
 +      <section>
            <h1 class="page-header">Pricing</h1>
        </section>
        <section id="quote">
  
    <template id="change_quantity" inherit_id="website_quote.pricing" active="False" customize_show="True" name="Change Quantity">
        <xpath expr="//div[@id='quote_qty']" position="replace">
-           <div class="input-group">
+           <div class="input-group oe_website_spinner">
                <span class="input-group-addon hidden-print">
                    <a t-attf-href="./update_line/#{ line.id }/?order_id=#{ quotation.id }&amp;remove=True&amp;token=#{ quotation.access_token }" class="mb8 js_update_line_json">
                        <span class="fa fa-minus"/>
                                </div>
  
  
-                               <div class="text-center mb16" t-if="quotation.amount_undiscounted &gt; quotation.amount_total">
+                               <div class="text-center mb16" t-if="quotation.amount_undiscounted &gt; quotation.amount_untaxed">
                                    <p class="text-muted mb8">Your advantage:</p>
-                                   <strong t-field="quotation.amount_total" 
-                                         t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'/>
-                                   <strong t-field="quotation.amount_undiscounted"
-                                         t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'
-                                         style="text-decoration: line-through"
-                                         class="text-danger"/>
+                                   <t t-if="quotation.amount_untaxed == quotation.amount_total">
+                                       <strong t-field="quotation.amount_total"
+                                           t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'/>
+                                       <strong t-field="quotation.amount_undiscounted"
+                                           t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'
+                                           style="text-decoration: line-through"
+                                           class="text-danger"/>
+                                   </t>
+                                   <t t-if="quotation.amount_untaxed != quotation.amount_total">
+                                       <strong t-field="quotation.amount_untaxed"
+                                           t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'/>
+                                       <strong t-field="quotation.amount_undiscounted"
+                                           t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'
+                                           style="text-decoration: line-through"
+                                           class="text-danger"/>
+                                       <br />
+                                       (<span t-field="quotation.amount_total"
+                                           t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'/> Incl. tax)
+                                   </t>
                                </div>
                           </div>
                        </div>
                            <em t-esc="quotation.name"/>
                            <small t-field="quotation.state"/>
                            <div groups="base.group_website_publisher" t-ignore="true" class="pull-right css_editable_mode_hidden">
 -                              <a class="btn btn-info hidden-print" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s' % (quotation._name, quotation.id)">Update Quote</a>
 +                              <a class="btn btn-info hidden-print" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Update Quote</a>
                            </div>
                        </h1>
  
                        </div>
  
                        <a id="offer"/>
 -                      <div t-field="quotation.website_description"/>
 +                      <div t-field="quotation.website_description" class="oe_no_empty"/>
  
                        <t t-foreach="quotation.order_line" t-as="line">
                            <a t-att-id="line.id"/>
 -                          <div t-field="line.website_description"/>
 +                          <div t-field="line.website_description" class="oe_no_empty"/>
                        </t>
      
                        <div class="oe_structure"/>
  
    <template id="optional_products">
      <div class="container mt32" t-if="option">
 -        <section data-snippet-id="title">
 +        <section>
              <h1 class="page-header">Options</h1>
          </section>
          <section id="options">
                                  table of content automatically.
                              </p>
                          </div>
 -                        <div id="template_introduction" t-field="template.website_description"/>
 +                        <div id="template_introduction" t-field="template.website_description" class="oe_no_empty"/>
                          <t t-foreach="template.quote_line" t-as="line">
                              <div class="alert alert-info alert-dismissable" t-ignore="True">
                                  <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
                                  this content will appear on the quotation only if this
                                  product is put on the quote.
                              </div>
 -                            <div t-field="line.website_description"/>
 +                            <div t-field="line.website_description" class="oe_no_empty"/>
                          </t>
                          <t t-foreach="template.options" t-as="option_line">
                              <div class="alert alert-info alert-dismissable" t-ignore="True">
                                  this content will appear on the quotation only if this
                                  product is used in the quote.
                              </div>
 -                            <div t-field="option_line.website_description"/>
 +                            <div t-field="option_line.website_description" class="oe_no_empty"/>
                          </t>
                          <section id="terms" class="container" t-if="template.note">
                              <h1 class="page-header" t-ignore="True">Terms &amp; Conditions</h1>
@@@ -168,11 -168,17 +168,11 @@@ class website_sale(http.Controller)
          else:
              pricelist = pool.get('product.pricelist').browse(cr, uid, context['pricelist'], context)
  
 -        product_obj = pool.get('product.template')
 -
          url = "/shop"
 -        product_count = product_obj.search_count(cr, uid, domain, context=context)
          if search:
              post["search"] = search
          if category:
              url = "/shop/category/%s" % slug(category)
 -        pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
 -        product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
 -        products = product_obj.browse(cr, uid, product_ids, context=context)
  
          style_obj = pool['product.style']
          style_ids = style_obj.search(cr, uid, [], context=context)
          categories = category_obj.browse(cr, uid, category_ids, context=context)
          categs = filter(lambda x: not x.parent_id, categories)
  
 +        domain += [('public_categ_ids', 'in', category_ids)]
 +        product_obj = pool.get('product.template')
 +
 +        product_count = product_obj.search_count(cr, uid, domain, context=context)
 +        pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
 +        product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
 +        products = product_obj.browse(cr, uid, product_ids, context=context)
 +
          attributes_obj = request.registry['product.attribute']
          attributes_ids = attributes_obj.search(cr, uid, [], context=context)
          attributes = attributes_obj.browse(cr, uid, attributes_ids, context=context)
  
          return values
  
-     mandatory_billing_fields = ["name", "phone", "email", "street", "city", "country_id", "zip"]
-     optional_billing_fields = ["street2", "state_id", "vat"]
+     mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id", "zip"]
+     optional_billing_fields = ["street", "state_id", "vat", "vat_subjected"]
      mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id", "zip"]
      optional_shipping_fields = ["state_id"]
  
                  for field_name in all_fields if data.get(prefix + field_name))
          else:
              query = dict((prefix + field_name, getattr(data, field_name))
-                 for field_name in all_fields if field_name != "street2" and getattr(data, field_name))
+                 for field_name in all_fields if getattr(data, field_name))
              if data.parent_id:
-                 query[prefix + 'street2'] = data.parent_id.name
+                 query[prefix + 'street'] = data.parent_id.name
  
          if query.get(prefix + 'state_id'):
              query[prefix + 'state_id'] = int(query[prefix + 'state_id'])
          if query.get(prefix + 'country_id'):
              query[prefix + 'country_id'] = int(query[prefix + 'country_id'])
  
+         if query.get(prefix + 'vat'):
+             query[prefix + 'vat_subjected'] = True
          if not remove_prefix:
              return query
  
              'partner_invoice_id': partner_id,
          }
          order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
-         order_info.update(order_obj.onchange_delivery_id(cr, SUPERUSER_ID, [], order.company_id.id, partner_id, checkout.get('shipping_id'), None, context=context)['value'])
+         address_change = order_obj.onchange_delivery_id(cr, SUPERUSER_ID, [], order.company_id.id, partner_id,
+                                                         checkout.get('shipping_id'), None, context=context)['value']
+         order_info.update(address_change)
+         if address_change.get('fiscal_position'):
+             fiscal_update = order_obj.onchange_fiscal_position(cr, SUPERUSER_ID, [], address_change['fiscal_position'],
+                                                                [(4, l.id) for l in order.order_line], context=None)['value']
+             order_info.update(fiscal_update)
  
          order_info.pop('user_id')
          order_info.update(partner_shipping_id=checkout.get('shipping_id') or partner_id)
          """
          cr, uid, context = request.cr, request.uid, request.context
          payment_obj = request.registry.get('payment.acquirer')
+         sale_order_obj = request.registry.get('sale.order')
  
          order = request.website.sale_get_order(context=context)
  
          values = {
              'order': request.registry['sale.order'].browse(cr, SUPERUSER_ID, order.id, context=context)
          }
-         values.update(request.registry.get('sale.order')._get_website_data(cr, uid, order, context))
+         values['errors'] = sale_order_obj._get_errors(cr, uid, order, context=context)
+         values.update(sale_order_obj._get_website_data(cr, uid, order, context))
  
          # fetch all registered payment means
          # if tx:
          #     acquirer_ids = [tx.acquirer_id.id]
          # else:
-         acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True), ('company_id', '=', order.company_id.id)], context=context)
-         values['acquirers'] = list(payment_obj.browse(cr, uid, acquirer_ids, context=context))
-         render_ctx = dict(context, submit_class='btn btn-primary', submit_txt=_('Pay Now'))
-         for acquirer in values['acquirers']:
-             acquirer.button = payment_obj.render(
-                 cr, SUPERUSER_ID, acquirer.id,
-                 order.name,
-                 order.amount_total,
-                 order.pricelist_id.currency_id.id,
-                 partner_id=shipping_partner_id,
-                 tx_values={
-                     'return_url': '/shop/payment/validate',
-                 },
-                 context=render_ctx)
+         if not values['errors']:
+             acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True), ('company_id', '=', order.company_id.id)], context=context)
+             values['acquirers'] = list(payment_obj.browse(cr, uid, acquirer_ids, context=context))
+             render_ctx = dict(context, submit_class='btn btn-primary', submit_txt=_('Pay Now'))
+             for acquirer in values['acquirers']:
+                 acquirer.button = payment_obj.render(
+                     cr, SUPERUSER_ID, acquirer.id,
+                     order.name,
+                     order.amount_total,
+                     order.pricelist_id.currency_id.id,
+                     partner_id=shipping_partner_id,
+                     tx_values={
+                         'return_url': '/shop/payment/validate',
+                     },
+                     context=render_ctx)
  
          return request.website.render("website_sale.payment", values)
  
                  'sale_order_id': order.id,
              }, context=context)
              request.session['sale_transaction_id'] = tx_id
 +            tx = transaction_obj.browse(cr, SUPERUSER_ID, tx_id, context=context)
  
          # update quotation
          request.registry['sale.order'].write(
                  'payment_tx_id': request.session['sale_transaction_id']
              }, context=context)
  
 +        # confirm the quotation
 +        if tx.acquirer_id.auto_confirm == 'at_pay_now':
 +            request.registry['sale.order'].action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
 +
          return tx_id
  
      @http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
                            t-attf-class="oe_product oe_grid oe-height-#{td_product['y']*2} #{ td_product['class'] }">
  
                            <div class="oe_product_cart" t-att-data-publish="product.website_published and 'on' or 'off'">
 -
 -                            <div class="css_options" t-ignore="true" groups="base.group_website_publisher">
 -                              <div t-attf-class="dropdown js_options" t-att-data-id="product.id">
 -                                <button class="btn btn-default" t-att-id="'dopprod-%s' % product.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></button>
 -                                <ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % product.id">
 -                                  <li class='dropdown-submenu'>
 -                                    <a tabindex="-1" href="#">Size</a>
 -                                    <ul class="dropdown-menu" name="size">
 -                                      <li><a href="#">
 -                                        <table>
 -                                          <tr>
 -                                            <td class="selected"></td>
 -                                            <td t-att-class="product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                          <tr>
 -                                            <td t-att-class="product.website_size_y > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 1 and product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 1 and product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 1 and product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                          <tr>
 -                                            <td t-att-class="product.website_size_y > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 2 and product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 2 and product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 2 and product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                          <tr>
 -                                            <td t-att-class="product.website_size_y > 3 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 3 and product.website_size_x > 1 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 3 and product.website_size_x > 2 and 'selected'"></td>
 -                                            <td t-att-class="product.website_size_y > 3 and product.website_size_x > 3 and 'selected'"></td>
 -                                          </tr>
 -                                        </table>
 -                                      </a></li>
 -                                    </ul>
 -                                  </li>
 -                                  <li class='dropdown-submenu'>
 -                                    <a tabindex="-1" href="#">Styles</a>
 -                                    <ul class="dropdown-menu" name="style">
 -                                      <t t-foreach="styles" t-as="style">
 -                                        <li t-att-class="style_in_product(style, product) and 'active' or ''"><a href="#" t-att-data-id="style.id" t-att-data-class="style.html_class"><t t-esc="style.name"/></a></li>
 -                                      </t>
 -                                    </ul>
 -                                  </li>
 -                                  <li class='dropdown-submenu'>
 -                                      <a tabindex="-1" href="#">Promote</a>
 -                                      <ul class="dropdown-menu" name="sequence">
 -                                          <li><a href="#" class="js_go_to_top">Push to top</a></li>
 -                                          <li><a href="#" class="js_go_up">Push up</a>
 -                                          </li>
 -                                          <li><a href="#" class="js_go_down">Push down</a></li>
 -                                          <li><a href="#" class="js_go_to_bottom">Push to bottom</a></li>
 -                                      </ul>
 -                                  </li>
 -                                </ul>
 -                              </div>
 -                            </div>
                              <t t-set="product_image_big" t-value="td_product['x']+td_product['y'] > 2"/>
                              <t t-call="website_sale.products_item"/>
                            </div>
  
  <template id="product_quantity" inherit_id="website_sale.product" customize_show="True" name="Select Quantity">
    <xpath expr="//a[@id='add_to_cart']" position="before">
-     <div class="css_quantity input-group" style="width: 108px;">
+     <div class="css_quantity input-group oe_website_spinner">
          <span class="input-group-addon">
              <a t-attf-href="#" class="mb8 js_add_cart_json">
                  <i class="fa fa-minus"></i>
                                   }'/>
                                </td>
                                <td class="text-center">
-                                   <div class="input-group">
+                                   <div class="input-group oe_website_spinner">
                                        <span class="input-group-addon">
                                            <a t-attf-href="#" class="mb8 js_add_cart_json" data-no-instant="">
                                                <i class="fa fa-minus"></i>
                        <input type="text" name="name" class="form-control" t-att-value="checkout.get('name')"/>
                    </div>
                    <div t-if="has_check_vat" class="clearfix"/>
-                   <div t-attf-class="form-group #{error.get('street2') and 'has-error' or ''} col-lg-6">
-                       <label class="control-label" for="street2" style="font-weight: normal">Company Name</label>
-                       <input type="text" name="street2" class="form-control" t-att-value="checkout.get('street2')"/>
+                   <div t-attf-class="form-group #{error.get('street') and 'has-error' or ''} col-lg-6">
+                       <label class="control-label" for="street" style="font-weight: normal">Company Name</label>
+                       <input type="text" name="street" class="form-control" t-att-value="checkout.get('street')"/>
                    </div>
                    <div t-if="has_check_vat" t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-lg-6">
                        <label class="control-label" for="vat" style="font-weight: normal">VAT Number</label>
                        <input type="tel" name="phone" class="form-control" t-att-value="checkout.get('phone')"/>
                    </div>
  
-                   <div t-attf-class="form-group #{error.get('street') and 'has-error' or ''} col-lg-6">
-                       <label class="control-label" for="street">Street</label>
-                       <input type="text" name="street" class="form-control" t-att-value="checkout.get('street')"/>
+                   <div t-attf-class="form-group #{error.get('street2') and 'has-error' or ''} col-lg-6">
+                       <label class="control-label" for="street2">Street</label>
+                       <input type="text" name="street2" class="form-control" t-att-value="checkout.get('street2')"/>
                    </div>
                    <div class="clearfix"/>
  
            </ul>
            <h1 class="mb32">Validate Order</h1>
            <div class="row">
+           <div class="col-lg-8 col-sm-9">
+               <t t-foreach="errors" t-as="error">
+               <div class="alert alert-danger" t-if="error">
+                 <h4><t t-esc="error[0]"/></h4>
+                 <t t-esc="error[1]"/>
+               </div>
+             </t>
+           </div>
            <div class="col-lg-8 col-sm-9 oe_cart">
                <t t-set="website_sale_order" t-value="website.sale_get_order()"/>
                <table class='table table-striped table-condensed' id="cart_products" t-if="website_sale_order and website_sale_order.website_order_line">
                    <h2>Thank you for your order.</h2>
                    <div class="oe_website_sale_tx_status" t-att-data-order-id="order.id">
                    </div>
 +                  <h3 class="mt32"><strong>Order Details:</strong></h3>
 +                  <table class="table">
 +                      <thead>
 +                          <tr>
 +                              <th>Products</th>
 +                              <th>Quantity</th>
 +                              <th class="text-right" width="100">Unit Price</th>
 +                              <th class="text-right" width="100">Subtotal</th>
 +                          </tr>
 +                      </thead>
 +                      <tbody>
 +                          <tr t-foreach="order.order_line" t-as="line">
 +                              <td>
 +                                  <div>
 +                                      <a t-attf-href="/shop/product/#{ slug(line.product_id.product_tmpl_id) }">
 +                                          <strong t-esc="line.product_id.name_get()[0][1]"/>
 +                                      </a>
 +                                  </div>
 +                                  <div class="text-muted" t-field="line.name"/>
 +                              </td>
 +                              <td>
 +                                  <div id="quote_qty">
 +                                      <span t-field="line.product_uom_qty"/>
 +                                      <span t-field="line.product_uom"/>
 +                                  </div>
 +                              </td>
 +                              <td>
 +                                  <strong class="text-right">
 +                                      <div t-field="line.price_unit"
 +                                          t-field-options='{"widget": "monetary", "display_currency": "order.pricelist_id.currency_id"}'/>
 +                                  </strong>
 +                              </td>
 +                              <td>
 +                                  <div class="text-right"
 +                                      t-field="line.price_subtotal"
 +                                      t-field-options='{"widget": "monetary", "display_currency": "order.pricelist_id.currency_id"}'/>
 +                              </td>
 +                          </tr>
 +                          <tr>
 +                              <td></td><td></td>
 +                              <td class="text-right"><strong>Total:</strong></td>
 +                              <td class="text-right">
 +                                  <strong t-field="order.amount_total"
 +                                      t-field-options='{"widget": "monetary", "display_currency": "order.pricelist_id.currency_id"}'/>
 +                              </td>
 +                          </tr>
 +                      </tbody>
 +                  </table>
                    <div class="clearfix"/>
                    <div class="oe_structure"/>
                </div>
diff --combined doc/conf.py
@@@ -44,7 -44,7 +44,7 @@@ source_suffix = '.rst
  master_doc = 'index'
  
  # General information about the project.
- project = u'odoo developer documentation'
+ project = u'odoo'
  copyright = u'2014, OpenERP s.a.'
  
  # The version info for the project you're documenting, acts as replacement for
@@@ -52,9 -52,9 +52,9 @@@
  # built documents.
  #
  # The short X.Y version.
 -version = '8.0'
 +version = 'master'
  # The full version, including alpha/beta/rc tags.
 -release = '8.0b1'
 +release = 'master'
  
  # There are two options for replacing |today|: either, you set today to some
  # non-false value, then it is used:
@@@ -167,7 -167,9 +167,9 @@@ html_sidebars = 
  
  intersphinx_mapping = {
      'python': ('https://docs.python.org/2/', None),
-     'werkzeug': ('http://werkzeug.pocoo.org/docs/0.9/', None),
+     'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
+     'sqlalchemy': ('http://docs.sqlalchemy.org/en/rel_0_9/', None),
+     'django': ('https://django.readthedocs.org/en/latest/', None),
  }
  
  github_user = 'odoo'
@@@ -30,6 -30,8 +30,8 @@@ from openerp.tools.translate import 
  from openerp.exceptions import AccessError
  from openerp.osv import fields,osv
  from openerp import SUPERUSER_ID
+ from openerp.osv.orm import except_orm
+ from openerp.tools.translate import _
  
  _logger = logging.getLogger(__name__)
  
@@@ -224,12 -226,14 +226,14 @@@ class ir_attachment(osv.osv)
          more complex ones apply there.
          """
          res_ids = {}
+         require_employee = False
          if ids:
              if isinstance(ids, (int, long)):
                  ids = [ids]
              cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,))
              for rmod, rid in cr.fetchall():
                  if not (rmod and rid):
+                     require_employee = True
                      continue
                  res_ids.setdefault(rmod,set()).add(rid)
          if values:
              # ignore attachments that are not attached to a resource anymore when checking access rights
              # (resource was deleted but attachment was not)
              if not self.pool.get(model):
+                 require_employee = True
                  continue
-             mids = self.pool[model].exists(cr, uid, mids)
+             existing_ids = self.pool[model].exists(cr, uid, mids)
+             if len(existing_ids) != len(mids):
+                 require_employee = True
              ima.check(cr, uid, model, mode)
-             self.pool[model].check_access_rule(cr, uid, mids, mode, context=context)
+             self.pool[model].check_access_rule(cr, uid, existing_ids, mode, context=context)
+         if require_employee:
+             if not self.pool['res.users'].has_group(cr, uid, 'base.group_user'):
+                 raise except_orm(_('Access Denied'), _("Sorry, you are not allowed to access this document."))
  
      def _search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
          ids = super(ir_attachment, self)._search(cr, uid, args, offset=offset,
          return self.pool.get('ir.actions.act_window').for_xml_id(
              cr, uid, 'base', 'action_attachment', context=context)
  
 +    def invalidate_bundle(self, cr, uid, type='%', xmlid=None, context=None):
 +        assert type in ('%', 'css', 'js'), "Unhandled bundle type"
 +        xmlid = '%' if xmlid is None else xmlid + '%'
 +        domain = [('url', '=like', '/web/%s/%s/%%' % (type, xmlid))]
 +        ids = self.search(cr, uid, domain, context=context)
 +        if ids:
 +            self.unlink(cr, uid, ids, context=context)
 +
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -27,7 -27,7 +27,7 @@@ import type
  
  import openerp
  from openerp import SUPERUSER_ID
- from openerp import models, tools
+ from openerp import models, tools, api
  from openerp.modules.registry import RegistryManager
  from openerp.osv import fields, osv
  from openerp.osv.orm import BaseModel, Model, MAGIC_COLUMNS, except_orm
@@@ -181,6 -181,7 +181,7 @@@ class ir_model(osv.osv)
              # only reload pool for normal unlink. For module uninstall the
              # reload is done independently in openerp.modules.loading
              cr.commit() # must be committed before reloading registry in new cursor
+             api.Environment.reset()
              RegistryManager.new(cr.dbname)
              RegistryManager.signal_registry_change(cr.dbname)
  
@@@ -527,7 -528,6 +528,7 @@@ class ir_model_constraint(Model)
      _columns = {
          'name': fields.char('Constraint', required=True, select=1,
              help="PostgreSQL constraint or foreign key name."),
 +        'definition': fields.char('Definition', help="PostgreSQL constraint definition"),
          'model': fields.many2one('ir.model', string='Model',
              required=True, select=1),
          'module': fields.many2one('ir.module.module', string='Module',
@@@ -80,6 -80,9 +80,9 @@@ class QWebContext(dict)
          return eval(expr, None, locals_dict, nocopy=True, locals_builtins=True)
  
      def copy(self):
+         """ Clones the current context, conserving all data and metadata
+         (loader, template cache, ...)
+         """
          return QWebContext(self.cr, self.uid, dict.copy(self),
                             loader=self.loader,
                             templates=self.templates,
          return self.copy()
  
  class QWeb(orm.AbstractModel):
-     """QWeb Xml templating engine
-     The templating engine use a very simple syntax based "magic" xml
-     attributes, to produce textual output (even non-xml).
-     The core magic attributes are:
-     flow attributes:
-         t-if t-foreach t-call
-     output attributes:
-         t-att t-raw t-esc t-trim
+     """ Base QWeb rendering engine
  
-     assignation attribute:
-         t-set
-     QWeb can be extended like any OpenERP model and new attributes can be
-     added.
-     If you need to customize t-fields rendering, subclass the ir.qweb.field
-     model (and its sub-models) then override :meth:`~.get_converter_for` to
-     fetch the right field converters for your qweb model.
+     * to customize ``t-field`` rendering, subclass ``ir.qweb.field`` and
+       create new models called :samp:`ir.qweb.field.{widget}`
+     * alternatively, override :meth:`~.get_converter_for` and return an
+       arbitrary model to use as field converter
  
      Beware that if you need extensions or alterations which could be
      incompatible with other subsystems, you should create a local object
      def load_document(self, document, res_id, qwebcontext):
          """
          Loads an XML document and installs any contained template in the engine
+         :type document: a parsed lxml.etree element, an unparsed XML document
+                         (as a string) or the path of an XML file to load
          """
-         if hasattr(document, 'documentElement'):
+         if not isinstance(document, basestring):
+             # assume lxml.etree.Element
              dom = document
          elif document.startswith("<?xml"):
              dom = etree.fromstring(document)
          else:
-             dom = etree.parse(document)
+             dom = etree.parse(document).getroot()
  
          for node in dom:
              if node.get('t-name'):
                  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:
          return int(bool(self.eval(expr, qwebcontext)))
  
      def render(self, cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None):
+         """ render(cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None)
+         Renders the template specified by the provided template name
+         :param qwebcontext: context for rendering the template
+         :type qwebcontext: dict or :class:`QWebContext` instance
+         :param loader: if ``qwebcontext`` is a dict, loader set into the
+                        context instantiated for rendering
+         """
          if qwebcontext is None:
              qwebcontext = {}
  
          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":
              if attribute_name.startswith("t-"):
                  for attribute in self._render_att:
                      if attribute_name[2:].startswith(attribute):
-                         att, val = self._render_att[attribute](self, element, attribute_name, attribute_value, qwebcontext)
-                         if val:
+                         attrs = self._render_att[attribute](
+                             self, element, attribute_name, attribute_value, qwebcontext)
+                         for att, val in attrs:
+                             if not val: continue
+                             if not isinstance(val, str):
+                                 val = unicode(val).encode('utf-8')
                              generated_attributes += self.render_attribute(element, att, val, qwebcontext)
                          break
                  else:
              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-"):
-             att, val = attribute_name[7:], self.eval_format(attribute_value, qwebcontext)
-         elif attribute_name.startswith("t-att-"):
-             att, val = attribute_name[6:], self.eval(attribute_value, qwebcontext)
-         else:
-             att, val = self.eval_object(attribute_value, qwebcontext)
-         if val and not isinstance(val, str):
-             val = unicode(val).encode("utf8")
-         return att, val
+             return [(attribute_name[7:], self.eval_format(attribute_value, qwebcontext))]
+         if attribute_name.startswith("t-att-"):
+             return [(attribute_name[6:], self.eval(attribute_value, qwebcontext))]
+         result = self.eval_object(attribute_value, qwebcontext)
+         if isinstance(result, collections.Mapping):
+             return result.iteritems()
+         # assume tuple
+         return [result]
  
      # Tags
      def render_tag_raw(self, element, template_attributes, generated_attributes, qwebcontext):
          inner = widget.format(template_attributes['esc'], options, qwebcontext)
          return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner)
  
+     def _iterate(self, iterable):
+         if isinstance (iterable, collections.Mapping):
+             return iterable.iteritems()
+         return itertools.izip(*itertools.tee(iterable))
      def render_tag_foreach(self, element, template_attributes, generated_attributes, qwebcontext):
          expr = template_attributes["foreach"]
          enum = self.eval_object(expr, qwebcontext)
          if enum is None:
              template = qwebcontext.get('__template__')
              raise QWebException("foreach enumerator %r is not defined while rendering template %r" % (expr, template), template=template)
+         if isinstance(enum, int):
+             enum = range(enum)
  
          varname = template_attributes['as'].replace('.', '_')
          copy_qwebcontext = qwebcontext.copy()
-         size = -1
+         size = None
          if isinstance(enum, collections.Sized):
              size = len(enum)
-         copy_qwebcontext["%s_size" % varname] = size
+             copy_qwebcontext["%s_size" % varname] = size
          copy_qwebcontext["%s_all" % varname] = enum
          ru = []
-         for index, item in enumerate(enum):
+         for index, (item, value) in enumerate(self._iterate(enum)):
              copy_qwebcontext.update({
                  varname: item,
-                 '%s_value' % varname: item,
+                 '%s_value' % varname: value,
                  '%s_index' % varname: index,
                  '%s_first' % varname: index == 0,
-                 '%s_last' % varname: index + 1 == size,
              })
+             if size is not None:
+                 copy_qwebcontext['%s_last' % varname] = index + 1 == size
              if index % 2:
                  copy_qwebcontext.update({
                      '%s_parity' % varname: 'odd',
                                   element, template_attributes, generated_attributes, qwebcontext, context=qwebcontext.context)
  
      def get_converter_for(self, field_type):
+         """ returns a :class:`~openerp.models.Model` used to render a
+         ``t-field``.
+         By default, tries to get the model named
+         :samp:`ir.qweb.field.{field_type}`, falling back on ``ir.qweb.field``.
+         :param str field_type: type or widget of field to render
+         """
          return self.pool.get('ir.qweb.field.' + field_type, self.pool['ir.qweb.field'])
  
      def get_widget_for(self, widget):
+         """ returns a :class:`~openerp.models.Model` used to render a
+         ``t-esc``
+         :param str widget: name of the widget to use, or ``None``
+         """
          widget_model = ('ir.qweb.widget.' + widget) if widget else 'ir.qweb.widget'
          return self.pool.get(widget_model) or self.pool['ir.qweb.widget']
  
@@@ -502,7 -527,8 +538,8 @@@ class FieldConverter(osv.AbstractModel)
      def attributes(self, cr, uid, field_name, record, options,
                     source_element, g_att, t_att, qweb_context,
                     context=None):
-         """
+         """ attributes(cr, uid, field_name, record, options, source_element, g_att, t_att, qweb_context, context=None)
          Generates the metadata attributes (prefixed by ``data-oe-`` for the
          root node of the field conversion. Attribute values are escaped by the
          parent.
          ]
  
      def value_to_html(self, cr, uid, value, column, options=None, context=None):
-         """ Converts a single value to its HTML version/output
+         """ value_to_html(cr, uid, value, column, options=None, context=None)
+         Converts a single value to its HTML version/output
          """
          if not value: return ''
          return value
  
      def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
-         """ Converts the specified field of the browse_record ``record`` to
-         HTML
+         """ record_to_html(cr, uid, field_name, record, column, options=None, context=None)
+         Converts the specified field of the browse_record ``record`` to HTML
          """
          return self.value_to_html(
              cr, uid, record[field_name], column, options=options, context=context)
  
      def to_html(self, cr, uid, field_name, record, options,
                  source_element, t_att, g_att, qweb_context, context=None):
-         """ Converts a ``t-field`` to its HTML output. A ``t-field`` may be
+         """ to_html(cr, uid, field_name, record, options, source_element, t_att, g_att, qweb_context, context=None)
+         Converts a ``t-field`` to its HTML output. A ``t-field`` may be
          extended by a ``t-field-options``, which is a JSON-serialized mapping
          of configuration values.
  
  
      def render_element(self, cr, uid, source_element, t_att, g_att,
                         qweb_context, content):
-         """ Final rendering hook, by default just calls ir.qweb's ``render_element``
+         """ render_element(cr, uid, source_element, t_att, g_att, qweb_context, content)
+         Final rendering hook, by default just calls ir.qweb's ``render_element``
          """
          return self.qweb_object().render_element(
              source_element, t_att, g_att, qweb_context, content or '')
  
      def user_lang(self, cr, uid, context):
-         """
+         """ user_lang(cr, uid, context)
          Fetches the res.lang object corresponding to the language code stored
          in the user's context. Fallbacks to en_US if no lang is present in the
          context *or the language code is not valid*.
@@@ -904,7 -938,7 +949,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': field_browse.phone,
              'mobile': field_browse.mobile,
              'fax': field_browse.fax,
@@@ -1022,8 -1056,17 +1067,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 get_cache(self, type):
          content = None
          domain = [('url', '=', '/web/%s/%s/%s' % (type, self.xmlid, self.version))]
-         bundle = self.registry['ir.attachment'].search_read(self.cr, self.uid, domain, ['datas'], context=self.context)
+         bundle = self.registry['ir.attachment'].search_read(self.cr, openerp.SUPERUSER_ID, domain, ['datas'], context=self.context)
          if bundle and bundle[0]['datas']:
              content = bundle[0]['datas'].decode('base64')
          return content
  
      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
  
@@@ -1297,7 -1320,7 +1342,7 @@@ class WebAsset(object)
                      fields = ['__last_update', 'datas', 'mimetype']
                      domain = [('type', '=', 'binary'), ('url', '=', self.url)]
                      ira = self.registry['ir.attachment']
-                     attach = ira.search_read(self.cr, self.uid, domain, fields, context=self.context)
+                     attach = ira.search_read(self.cr, openerp.SUPERUSER_ID, domain, fields, context=self.context)
                      self._ir_attach = attach[0]
                  except Exception:
                      raise AssetNotFound("Could not find %s" % self.name)
  
      @property
      def content(self):
 -        if not self._content:
 +        if self._content is None:
              self._content = self.inline or self._fetch_content()
          return self._content
  
@@@ -1390,26 -1413,22 +1435,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,
 +                )
 +
 +            if self.rx_charset:
 +                # remove charset declarations, we only support utf-8
 +                content = self.rx_charset.sub('', content)
  
 -            # 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, self.uid, domain, context=self.context)
+             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):
 +        return ['sass', '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
 +               '-r', 'bootstrap-sass']
 +
 +class LessStylesheetAsset(PreprocessedCSS):
 +    def get_command(self):
 +        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,37 -123,9 +150,37 @@@ 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'),
              ('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_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'),
          'field_parent': fields.char('Child Field'),
          return values
  
      def create(self, cr, uid, values, context=None):
-         if 'type' not in values:
+         if not values.get('type'):
              if values.get('inherit_id'):
                  values['type'] = self.browse(cr, uid, values['inherit_id'], context).type
              else:
          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'\
              e.set('data-oe-xpath', node_path)
          if not e.get('data-oe-model'): return
  
-         if set(('t-esc', 't-escf', 't-raw', 't-rawf')).intersection(e.attrib):
+         if {'t-esc', 't-raw'}.intersection(e.attrib):
              # nodes which fully generate their content and have no reason to
              # be branded because they can not sensibly be edited
              self._pop_view_branding(e)
@@@ -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),
          if not context:
              context={}
          ids = []
-         if name:
+         if name and operator in ['=', 'ilike']:
              ids = self.search(cr, user, [('login','=',name)]+ args, limit=limit, context=context)
          if not ids:
              ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
diff --combined openerp/models.py
@@@ -238,8 -238,9 +238,9 @@@ class MetaModel(api.Meta)
  
          # transform columns into new-style fields (enables field inheritance)
          for name, column in self._columns.iteritems():
-             if not hasattr(self, name):
-                 setattr(self, name, column.to_field())
+             if name in self.__dict__:
+                 _logger.warning("In class %s, field %r overriding an existing value", self, name)
+             setattr(self, name, column.to_field())
  
  
  class NewId(object):
@@@ -460,16 -461,14 +461,14 @@@ class BaseModel(object)
      @classmethod
      def _add_field(cls, name, field):
          """ Add the given `field` under the given `name` in the class """
-         field.set_class_name(cls, name)
-         # add field in _fields (for reflection)
+         # add field as an attribute and in cls._fields (for reflection)
+         if not isinstance(getattr(cls, name, field), Field):
+             _logger.warning("In model %r, field %r overriding existing value", cls._name, name)
+         setattr(cls, name, field)
          cls._fields[name] = field
  
-         # add field as an attribute, unless another kind of value already exists
-         if isinstance(getattr(cls, name, field), Field):
-             setattr(cls, name, field)
-         else:
-             _logger.warning("In model %r, member %r is not a field", cls._name, name)
+         # basic setup of field
+         field.set_class_name(cls, name)
  
          if field.store:
              cls._columns[name] = field.to_column()
              )
              columns.update(cls._columns)
  
-             defaults = dict(parent_class._defaults)
-             defaults.update(cls._defaults)
              inherits = dict(parent_class._inherits)
              inherits.update(cls._inherits)
  
                  '_name': name,
                  '_register': False,
                  '_columns': columns,
-                 '_defaults': defaults,
                  '_inherits': inherits,
                  '_depends': depends,
                  '_constraints': constraints,
              '_name': name,
              '_register': False,
              '_columns': dict(cls._columns),
-             '_defaults': dict(cls._defaults),
+             '_defaults': {},            # filled by Field._determine_default()
              '_inherits': dict(cls._inherits),
              '_depends': dict(cls._depends),
              '_constraints': list(cls._constraints),
                  "TransientModels must have log_access turned on, " \
                  "in order to implement their access rights policy"
  
-         # retrieve new-style fields and duplicate them (to avoid clashes with
-         # inheritance between different models)
+         # retrieve new-style fields (from above registry class) and duplicate
+         # them (to avoid clashes with inheritance between different models)
          cls._fields = {}
-         for attr, field in getmembers(cls, Field.__instancecheck__):
+         above = cls.__bases__[0]
+         for attr, field in getmembers(above, Field.__instancecheck__):
              if not field.inherited:
-                 cls._add_field(attr, field.copy())
+                 cls._add_field(attr, field.new())
  
          # introduce magic fields
          cls._add_magic_fields()
          # check defaults
          for k in cls._defaults:
              assert k in cls._fields, \
 -                "Model %s has a default for nonexiting field %s" % (cls._name, k)
 +                "Model %s has a default for non-existing field %s" % (cls._name, k)
  
          # restart columns
          for column in cls._columns.itervalues():
                  except Exception, e:
                      raise ValidationError("Error while validating constraint\n\n%s" % tools.ustr(e))
  
-     def default_get(self, cr, uid, fields_list, context=None):
+     @api.model
+     def default_get(self, fields_list):
          """ default_get(fields) -> default_values
  
          Return default values for the fields in `fields_list`. Default
  
          :param fields_list: a list of field names
          :return: a dictionary mapping each field name to its corresponding
-             default value; the keys of the dictionary are the fields in
-             `fields_list` that have a default value different from ``False``.
+             default value, if it has one.
  
-         This method should not be overridden. In order to change the
-         mechanism for determining default values, you should override method
-         :meth:`add_default_value` instead.
          """
          # trigger view init hook
-         self.view_init(cr, uid, fields_list, context)
+         self.view_init(fields_list)
+         defaults = {}
+         parent_fields = defaultdict(list)
  
-         # use a new record to determine default values; evaluate fields on the
-         # new record and put default values in result
-         record = self.new(cr, uid, {}, context=context)
-         result = {}
          for name in fields_list:
-             if name in self._fields:
-                 value = record[name]
-                 if name in record._cache:
-                     result[name] = value        # it really is a default value
+             # 1. look up context
+             key = 'default_' + name
+             if key in self._context:
+                 defaults[name] = self._context[key]
+                 continue
  
-         # convert default values to the expected format
-         result = self._convert_to_write(result)
-         return result
+             # 2. look up ir_values
+             #    Note: performance is good, because get_defaults_dict is cached!
+             ir_values_dict = self.env['ir.values'].get_defaults_dict(self._name)
+             if name in ir_values_dict:
+                 defaults[name] = ir_values_dict[name]
+                 continue
  
-     def add_default_value(self, field):
-         """ Set the default value of `field` to the new record `self`.
-             The value must be assigned to `self`.
-         """
-         assert not self.id, "Expected new record: %s" % self
-         cr, uid, context = self.env.args
-         name = field.name
+             field = self._fields.get(name)
  
-         # 1. look up context
-         key = 'default_' + name
-         if key in context:
-             self[name] = context[key]
-             return
+             # 3. look up property fields
+             #    TODO: get rid of this one
+             if field and field.company_dependent:
+                 defaults[name] = self.env['ir.property'].get(name, self._name)
+                 continue
  
-         # 2. look up ir_values
-         #    Note: performance is good, because get_defaults_dict is cached!
-         ir_values_dict = self.env['ir.values'].get_defaults_dict(self._name)
-         if name in ir_values_dict:
-             self[name] = ir_values_dict[name]
-             return
+             # 4. look up field.default
+             if field and field.default:
+                 defaults[name] = field.default(self)
+                 continue
  
-         # 3. look up property fields
-         #    TODO: get rid of this one
-         column = self._columns.get(name)
-         if isinstance(column, fields.property):
-             self[name] = self.env['ir.property'].get(name, self._name)
-             return
+             # 5. delegate to parent model
+             if field and field.inherited:
+                 field = field.related_field
+                 parent_fields[field.model_name].append(field.name)
  
-         # 4. look up _defaults
-         if name in self._defaults:
-             value = self._defaults[name]
-             if callable(value):
-                 value = value(self._model, cr, uid, context)
-             self[name] = value
-             return
+         # convert default values to the right format
+         defaults = self._convert_to_cache(defaults, validate=False)
+         defaults = self._convert_to_write(defaults)
+         # add default values for inherited fields
+         for model, names in parent_fields.iteritems():
+             defaults.update(self.env[model].default_get(names))
  
-         # 5. delegate to field
-         field.determine_default(self)
+         return defaults
  
      def fields_get_keys(self, cr, user, context=None):
          res = self._columns.keys()
          qualified_field = self._inherits_join_calc(split[0], query)
          if temporal:
              display_formats = {
-                 'day': 'dd MMM YYYY', 
-                 'week': "'W'w YYYY", 
-                 'month': 'MMMM YYYY', 
-                 'quarter': 'QQQ YYYY', 
-                 'year': 'YYYY'
+                 # Careful with week/year formats:
+                 #  - yyyy (lower) must always be used, *except* for week+year formats
+                 #  - YYYY (upper) must always be used for week+year format
+                 #         e.g. 2006-01-01 is W52 2005 in some locales (de_DE),
+                 #                         and W1 2006 for others
+                 #
+                 # Mixing both formats, e.g. 'MMM YYYY' would yield wrong results,
+                 # such as 2006-01-01 being formatted as "January 2005" in some locales.
+                 # Cfr: http://babel.pocoo.org/docs/dates/#date-fields
+                 'day': 'dd MMM yyyy', # yyyy = normal year
+                 'week': "'W'w YYYY",  # w YYYY = ISO week-year
+                 'month': 'MMMM yyyy',
+                 'quarter': 'QQQ yyyy',
+                 'year': 'yyyy',
              }
              time_intervals = {
                  'day': dateutil.relativedelta.relativedelta(days=1),
                  if val is not False:
                      cr.execute(update_query, (ss[1](val), key))
  
-     def _check_selection_field_value(self, cr, uid, field, value, context=None):
-         """Raise except_orm if value is not among the valid values for the selection field"""
-         if self._columns[field]._type == 'reference':
-             val_model, val_id_str = value.split(',', 1)
-             val_id = False
-             try:
-                 val_id = long(val_id_str)
-             except ValueError:
-                 pass
-             if not val_id:
-                 raise except_orm(_('ValidateError'),
-                                  _('Invalid value for reference field "%s.%s" (last part must be a non-zero integer): "%s"') % (self._table, field, value))
-             val = val_model
-         else:
-             val = value
-         if isinstance(self._columns[field].selection, (tuple, list)):
-             if val in dict(self._columns[field].selection):
-                 return
-         elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
-             return
-         raise except_orm(_('ValidateError'),
-                          _('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._name, field))
+     @api.model
+     def _check_selection_field_value(self, field, value):
+         """ Check whether value is among the valid values for the given
+             selection/reference field, and raise an exception if not.
+         """
+         field = self._fields[field]
+         field.convert_to_cache(value, self)
  
      def _check_removed_columns(self, cr, log=False):
          # iterate on the database columns to drop the NOT NULL constraints
                  _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):
          """
  
  
      def _set_default_value_on_column(self, cr, column_name, context=None):
-         # ideally should use add_default_value but fails
-         # due to ir.values not being ready
+         # ideally, we should use default_get(), but it fails due to ir.values
+         # not being ready
  
-         # get old-style default
+         # get default value
          default = self._defaults.get(column_name)
          if callable(default):
              default = default(self, cr, SUPERUSER_ID, context)
  
-         # get new_style default if no old-style
-         if default is None:
-             record = self.new(cr, SUPERUSER_ID, context=context)
-             field = self._fields[column_name]
-             field.determine_default(record)
-             defaults = dict(record._cache)
-             if column_name in defaults:
-                 default = field.convert_to_write(defaults[column_name])
          column = self._columns[column_name]
          ss = column._symbol_set
          db_default = ss[1](default)
          """ 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']]:
          for parent_model, parent_field in reversed(cls._inherits.items()):
              for attr, field in cls.pool[parent_model]._fields.iteritems():
                  if attr not in cls._fields:
-                     cls._add_field(attr, field.copy(
+                     cls._add_field(attr, field.new(
                          inherited=True,
                          related=(parent_field, attr),
                          related_sudo=False,
      def _setup_fields(self, partial=False):
          """ Setup the fields (dependency triggers, etc). """
          for field in self._fields.itervalues():
-             if partial and field.manual and \
-                     field.relational and \
-                     (field.comodel_name not in self.pool or \
-                      (field.type == 'one2many' and field.inverse_name not in self.pool[field.comodel_name]._fields)):
-                 # do not set up manual fields that refer to unknown models
-                 continue
-             field.setup(self.env)
+             try:
+                 field.setup(self.env)
+             except Exception:
+                 if not partial:
+                     raise
  
          # group fields by compute to determine field.computed_fields
          fields_by_compute = defaultdict(list)
          for fname, field in self._fields.iteritems():
              if allfields and fname not in allfields:
                  continue
+             if not field.setup_done:
+                 continue
              if field.groups and not recs.user_has_groups(field.groups):
                  continue
              res[fname] = field.get_description(recs.env)
          if len(records) > PREFETCH_MAX:
              records = records[:PREFETCH_MAX] | self
  
-         # by default, simply fetch field
-         fnames = {field.name}
-         if self.env.in_draft:
-             # we may be doing an onchange, do not prefetch other fields
-             pass
-         elif self.env.field_todo(field):
-             # field must be recomputed, do not prefetch records to recompute
-             records -= self.env.field_todo(field)
-         elif not self._context.get('prefetch_fields', True):
-             # do not prefetch other fields
-             pass
-         elif self._columns[field.name]._prefetch:
-             # here we can optimize: prefetch all classic and many2one fields
-             fnames = set(fname
+         # determine which fields can be prefetched
+         if not self.env.in_draft and \
+                 self._context.get('prefetch_fields', True) and \
+                 self._columns[field.name]._prefetch:
+             # prefetch all classic and many2one fields that the user can access
+             fnames = {fname
                  for fname, fcolumn in self._columns.iteritems()
                  if fcolumn._prefetch
                  if not fcolumn.groups or self.user_has_groups(fcolumn.groups)
-             )
+             }
+         else:
+             fnames = {field.name}
+         # important: never prefetch fields to recompute!
+         get_recs_todo = self.env.field_todo
+         for fname in list(fnames):
+             if get_recs_todo(self._fields[fname]):
+                 if fname == field.name:
+                     records -= get_recs_todo(field)
+                 else:
+                     fnames.discard(fname)
  
          # fetch records with read()
          assert self in records and field.name in fnames
          # split up fields into old-style and pure new-style ones
          old_vals, new_vals, unknown = {}, {}, []
          for key, val in vals.iteritems():
-             if key in self._columns:
-                 old_vals[key] = val
-             elif key in self._fields:
-                 new_vals[key] = val
+             field = self._fields.get(key)
+             if field:
+                 if field.store or field.inherited:
+                     old_vals[key] = val
+                 if field.inverse and not field.inherited:
+                     new_vals[key] = val
              else:
                  unknown.append(key)
  
              upd0.append('write_uid=%s')
              upd0.append("write_date=(now() at time zone 'UTC')")
              upd1.append(user)
+             direct.append('write_uid')
+             direct.append('write_date')
  
          if len(upd0):
              self.check_access_rule(cr, user, ids, 'write', context=context)
                              self.write(cr, user, ids, {f: vals[f]}, context=context_wo_lang)
                          self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans)
  
+         # invalidate and mark new-style fields to recompute; do this before
+         # setting other fields, because it can require the value of computed
+         # fields, e.g., a one2many checking constraints on records
+         recs.modified(direct)
          # call the 'set' method of fields which are not classic_write
          upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
  
              for id in ids:
                  result += self._columns[field].set(cr, self, id, field, vals[field], user, context=rel_context) or []
  
+         # for recomputing new-style fields
+         recs.modified(upd_todo)
          unknown_fields = updend[:]
          for table in self._inherits:
              col = self._inherits[table]
          result += self._store_get_values(cr, user, ids, vals.keys(), context)
          result.sort()
  
-         # for recomputing new-style fields
-         recs.modified(modified_fields)
          done = {}
          for order, model_name, ids_to_update, fields_to_recompute in result:
              key = (model_name, tuple(fields_to_recompute))
          # split up fields into old-style and pure new-style ones
          old_vals, new_vals, unknown = {}, {}, []
          for key, val in vals.iteritems():
-             if key in self._all_columns:
-                 old_vals[key] = val
-             elif key in self._fields:
-                 new_vals[key] = val
+             field = self._fields.get(key)
+             if field:
+                 if field.store or field.inherited:
+                     old_vals[key] = val
+                 if field.inverse and not field.inherited:
+                     new_vals[key] = val
              else:
                  unknown.append(key)
  
  
          id_new, = cr.fetchone()
          recs = self.browse(cr, user, id_new, context)
-         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
  
          if self._parent_store and not context.get('defer_parent_store_computation'):
              if self.pool._init:
                  cr.execute('update '+self._table+' set parent_left=%s,parent_right=%s where id=%s', (pleft+1, pleft+2, id_new))
                  recs.invalidate_cache(['parent_left', 'parent_right'])
  
+         # invalidate and mark new-style fields to recompute; do this before
+         # setting other fields, because it can require the value of computed
+         # fields, e.g., a one2many checking constraints on records
+         recs.modified([u[0] for u in updates])
+         # call the 'set' method of fields which are not classic_write
+         upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
          # default element in context must be remove when call a one2many or many2many
          rel_context = context.copy()
          for c in context.items():
          for field in upd_todo:
              result += self._columns[field].set(cr, self, id_new, field, vals[field], user, rel_context) or []
  
+         # for recomputing new-style fields
+         recs.modified(upd_todo)
          # check Python constraints
          recs._validate_fields(vals)
  
-         # invalidate and mark new-style fields to recompute
-         modified_fields = list(vals)
-         if self._log_access:
-             modified_fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
-         recs.modified(modified_fields)
          if context.get('recompute', True):
              result += self._store_get_values(cr, user, [id_new],
                  list(set(vals.keys() + self._inherits.values())),
  
      #
      # New records - represent records that do not exist in the database yet;
-     # they are used to compute default values and perform onchanges.
+     # they are used to perform onchanges.
      #
  
      @api.model
diff --combined openerp/tools/config.py
@@@ -62,7 -62,7 +62,7 @@@ DEFAULT_LOG_HANDLER = [':INFO'
  
  def _get_default_datadir():
      home = os.path.expanduser('~')
 -    if os.path.exists(home):
 +    if os.path.isdir(home):
          func = appdirs.user_data_dir
      else:
          if sys.platform in ['win32', 'darwin']:
@@@ -261,7 -261,8 +261,7 @@@ class configmanager(object)
  
          # Advanced options
          group = optparse.OptionGroup(parser, "Advanced options")
 -        if os.name == 'posix':
 -            group.add_option('--auto-reload', dest='auto_reload', action='store_true', my_default=False, help='enable auto reload')
 +        group.add_option('--dev', dest='dev_mode', action='store_true', my_default=False, help='enable developper mode')
          group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
          group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
                            help="stop the server after its initialization")
                           type="int")
          group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
                           help="Use the unaccent function provided by the database when available.")
+         group.add_option("--geoip-db", dest="geoip_database", my_default='/usr/share/GeoIP/GeoLiteCity.dat',
+                          help="Absolute path to the GeoIP database file.")
          parser.add_option_group(group)
  
          if os.name == 'posix':
                  'db_maxconn', 'import_partial', 'addons_path',
                  'xmlrpc', 'syslog', 'without_demo', 'timezone',
                  'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
-                 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db'
-                 ]
+                 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db',
+                 'geoip_database',
+         ]
  
          for arg in keys:
              # Copy the command-line argument (except the special case for log_handler, due to
          # if defined but None take the configfile value
          keys = [
              'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
 -            'debug_mode', 'smtp_ssl', 'load_language',
 +            'debug_mode', 'dev_mode', 'smtp_ssl', 'load_language',
              'stop_after_init', 'logrotate', 'without_demo', 'xmlrpc', 'syslog',
              'list_db', 'xmlrpcs', 'proxy_mode',
              'test_file', 'test_enable', 'test_commit', 'test_report_directory',
          ]
  
          posix_keys = [
 -            'auto_reload', 'workers',
 +            'workers',
              'limit_memory_hard', 'limit_memory_soft',
              'limit_time_cpu', 'limit_time_real', 'limit_request',
          ]
diff --combined openerp/tools/mail.py
@@@ -49,7 -49,7 +49,7 @@@ allowed_tags = clean.defs.tags | frozen
  safe_attrs = clean.defs.safe_attrs | frozenset(
      ['style',
       'data-oe-model', 'data-oe-id', 'data-oe-field', 'data-oe-type', 'data-oe-expression', 'data-oe-translate', 'data-oe-nodeid',
 -     'data-snippet-id', 'data-publish', 'data-id', 'data-res_id', 'data-member_id', 'data-view-id'
 +     'data-publish', 'data-id', 'data-res_id', 'data-member_id', 'data-view-id'
       ])
  
  
@@@ -579,7 -579,7 +579,7 @@@ def append_content_to_html(html, conten
      elif plaintext:
          content = '\n%s\n' % plaintext2html(content, container_tag)
      else:
-         content = re.sub(r'(?i)(</?html.*>|</?body.*>|<!\W*DOCTYPE.*>)', '', content)
+         content = re.sub(r'(?i)(</?(?:html|body|head|!\s*DOCTYPE)[^>]*>)', '', content)
          content = u'\n%s\n' % ustr(content)
      # Force all tags to lowercase
      html = re.sub(r'(</?)\W*(\w+)([ >])',