[MERGE] forward port of branch saas-3 up to fdc6271
authorChristophe Simonis <chs@odoo.com>
Fri, 12 Sep 2014 16:53:48 +0000 (18:53 +0200)
committerChristophe Simonis <chs@odoo.com>
Fri, 12 Sep 2014 16:53:48 +0000 (18:53 +0200)
17 files changed:
1  2 
addons/email_template/email_template.py
addons/hr_expense/hr_expense.py
addons/mail/mail_thread.py
addons/mrp_repair/mrp_repair.py
addons/point_of_sale/static/src/js/models.js
addons/product/product_view.xml
addons/stock/stock_view.xml
addons/web/controllers/main.py
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/chrome.js
addons/web/static/src/js/data.js
addons/website_event_sale/controllers/main.py
openerp/addons/base/ir/ir_attachment.py
openerp/addons/base/ir/ir_mail_server.py
openerp/osv/orm.py
openerp/tools/mail.py

Simple merge
Simple merge
@@@ -353,10 -389,10 +353,10 @@@ class mrp_repair(osv.osv)
                      account_id = repair.partner_id.property_account_receivable.id
                      inv = {
                          'name': repair.name,
 -                        'origin':repair.name,
 +                        'origin': repair.name,
                          'type': 'out_invoice',
                          'account_id': account_id,
-                         'partner_id': repair.partner_id.id,
+                         'partner_id': repair.partner_invoice_id.id or repair.partner_id.id,
                          'currency_id': repair.pricelist_id.currency_id.id,
                          'comment': repair.quotation_notes,
                          'fiscal_position': repair.partner_id.property_account_position.id
              </field>
          </record>
  
 -        <record id="product_normal_action" model="ir.actions.act_window">
 +        <record id="product_template_action" model="ir.actions.act_window">
              <field name="name">Products</field>
              <field name="type">ir.actions.act_window</field>
 -            <field name="res_model">product.product</field>
 +            <field name="res_model">product.template</field>
 +            <field name="view_mode">kanban,tree,form</field>
              <field name="view_type">form</field>
 -            <field name="view_mode">tree,form,kanban</field>
 -            <field name="view_id" ref="product_product_tree_view"/>
 -            <field name="search_view_id" ref="product_search_form_view"/>
 -            <field name="help" type="html">
 -              <p class="oe_view_nocontent_create">
 -                Click to define a new product.
 -              </p><p>
 -                You must define a product for everything you buy or sell,
 -                whether it's a physical product, a consumable or service.
 -              </p>
 +            <field name="view_id" ref="product_template_kanban_view"/>
++            <field name="context">{"search_default_filter_to_sell":1}</field>
 +        </record>
 +
 +        <menuitem action="product_template_action"
 +            id="menu_product_template_action"
 +            parent="base.menu_product" sequence="1" />
 +
 +        <!-- product product -->
 +
 +        <menuitem id="prod_config_main" name="Product Variants" parent="base.menu_base_config" sequence="70" groups="base.group_no_one"/>
 +
 +        <record id="product_product_tree_view" model="ir.ui.view">
 +            <field name="name">product.product.tree</field>
 +            <field name="model">product.product</field>
 +            <field eval="7" name="priority"/>
 +            <field name="arch" type="xml">
 +
 +                <tree string="Product Variants">
 +                    <field name="default_code"/>
 +                    <field name="name"/>
 +                    <field name="attribute_value_ids" widget="many2many_tags"/>
 +                    <field name="lst_price"/>
 +                    <field name="price" invisible="not context.get('pricelist',False)"/>
 +                    <field name="uom_id"/>
 +                    <field name="ean13"/>
 +                    <field name="state" invisible="1"/>
 +                    <field name="product_tmpl_id" invisible="1"/>
 +                </tree>
 +
 +            </field>
 +        </record>
 +
 +        <record id="product_normal_form_view" model="ir.ui.view">
 +            <field name="name">product.product.form</field>
 +            <field name="model">product.product</field>
 +            <field name="mode">primary</field>
 +            <field eval="7" name="priority"/>
 +            <field name="inherit_id" ref="product.product_template_form_view"/>
 +            <field name="arch" type="xml">
 +                <form position="attributes">
 +                    <attribute name="string">Product Variant</attribute>
 +                </form>
 +                <field name="list_price" position="attributes">
 +                   <attribute name="name">lst_price</attribute>
 +                </field>
 +                <field name="name" position="replace">
 +                    <field name="name" attrs="{'invisible': [('id', '!=', False)]}"/>
 +                    <field name="product_tmpl_id" class="oe_inline" readonly="1" attrs="{'invisible': [('id', '=', False)], 'required': [('id', '!=', False)]}"/>
 +                </field>
 +                <xpath expr="//div[@class='oe_title']" position="inside">
 +                    <field name="attribute_value_ids" widget="many2many_tags"/>
 +                </xpath>
              </field>
          </record>
 +
 +        <record id="product_kanban_view" model="ir.ui.view">
 +            <field name="name">Product Kanban</field>
 +            <field name="model">product.product</field>
 +            <field name="mode">primary</field>
 +            <field name="inherit_id" ref="product.product_template_kanban_view"/>
 +            <field name="arch" type="xml">
 +                <field name="name" position="after">
 +                    <field name="attribute_value_ids"/>
 +                </field>
 +                <xpath expr="//img[@class='oe_kanban_image']" position="replace">
 +                    <img t-att-src="kanban_image('product.product', 'image_small', record.id.value)" class="oe_kanban_image"/>
 +                </xpath>
 +            </field>
 +        </record>
 +
 +        <!--  -->
 +
          <record id="product_normal_action_sell" model="ir.actions.act_window">
 -            <field name="name">Products</field>
 +            <field name="name">Product Variants</field>
              <field name="type">ir.actions.act_window</field>
              <field name="res_model">product.product</field>
              <field name="view_mode">kanban,tree,form</field>
                      <field name="name"/>
                      <field name="picking_id" string="Reference"/>
                      <field name="origin"/>
 -                    <field name="type" on_change="onchange_move_type(type)"/>
 +                    <field name="picking_type_id"/>
                      <field name="create_date" invisible="1" groups="base.group_no_one"/>
                      <field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, False)"/>
 -                    <field name="product_qty" on_change="onchange_quantity(product_id, product_qty, product_uom, product_uos)"/>
 +                    <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/>
                      <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
+                     <field name="product_uos_qty" groups="product.group_uos"/>
                      <field name="product_uos" groups="product.group_uos"/>
                      <button name="%(stock.move_scrap)d"
                          string="Scrap Products" type="action"
              <field name="arch" type="xml">
                  <tree colors="grey:scrapped == True" string="Stock Moves">
                      <field name="product_id"/>
 -                    <field name="product_qty" on_change="onchange_quantity(product_id, product_qty, product_uom, product_uos)"/>
 +                    <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/>
                      <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
+                     <field name="product_uos_qty" groups="product.group_uos"/>
                      <field name="product_uos" groups="product.group_uos"/>
                      <field name="location_id" groups="stock.group_locations" invisible="1"/>
                      <field name="picking_id" invisible="1" />
@@@ -116,10 -148,12 +116,10 @@@ def ensure_db(redirect='/web/database/s
              url_redirect += '?' + r.query_string
          response = werkzeug.utils.redirect(url_redirect, 302)
          request.session.db = db
 -        response = r.app.get_response(r, response, explicit_session=False)
 -        werkzeug.exceptions.abort(response)
 -        return
 +        abort_and_redirect(url_redirect)
  
      # if db not provided, use the session one
-     if not db:
+     if not db and http.db_filter([request.session.db]):
          db = request.session.db
  
      # if no database provided and no database in session, use monodb
@@@ -545,49 -682,87 +545,51 @@@ class Home(http.Controller)
  
      @http.route('/login', type='http', auth="none")
      def login(self, db, login, key, redirect="/web", **kw):
+         if not http.db_filter([db]):
+             return werkzeug.utils.redirect('/', 303)
          return login_and_redirect(db, login, key, redirect_url=redirect)
  
 -class WebClient(http.Controller):
 -
 -    @http.route('/web/webclient/csslist', type='json', auth="none")
 -    def csslist(self, mods=None):
 -        return manifest_list('css', mods=mods)
 -
 -    @http.route('/web/webclient/jslist', type='json', auth="none")
 -    def jslist(self, mods=None):
 -        return manifest_list('js', mods=mods)
 -
 -    @http.route('/web/webclient/qweblist', type='json', auth="none")
 -    def qweblist(self, mods=None):
 -        return manifest_list('qweb', mods=mods)
 -
 -    @http.route('/web/webclient/css', type='http', auth="none")
 -    def css(self, mods=None, db=None):
 -        files = list(manifest_glob('css', addons=mods, db=db))
 -        last_modified = get_last_modified(f[0] for f in files)
 -        if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
 -            return werkzeug.wrappers.Response(status=304)
 -
 -        file_map = dict(files)
 -
 -        rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
 -        rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
 -
 -        def reader(f):
 -            """read the a css file and absolutify all relative uris"""
 -            with open(f, 'rb') as fp:
 -                data = fp.read().decode('utf-8')
 -
 -            path = file_map[f]
 -            web_dir = os.path.dirname(path)
 -
 -            data = re.sub(
 -                rx_import,
 -                r"""@import \1%s/""" % (web_dir,),
 -                data,
 -            )
 -
 -            data = re.sub(
 -                rx_url,
 -                r"url(\1%s/" % (web_dir,),
 -                data,
 -            )
 -            return data.encode('utf-8')
 +    @http.route('/web/js/<xmlid>', type='http', auth="public")
 +    def js_bundle(self, xmlid, **kw):
 +        # manifest backward compatible mode, to be removed
 +        values = {'manifest_list': manifest_list}
 +        try:
 +            assets_html = request.render(xmlid, lazy=False, qcontext=values)
 +        except QWebTemplateNotFound:
 +            return request.not_found()
 +        bundle = AssetsBundle(xmlid, assets_html, debug=request.debug)
  
 -        content, checksum = concat_files((f[0] for f in files), reader)
 +        response = request.make_response(
 +            bundle.js(), [('Content-Type', 'application/javascript')])
  
 -        # move up all @import and @charset rules to the top
 -        matches = []
 -        def push(matchobj):
 -            matches.append(matchobj.group(0))
 -            return ''
 +        # TODO: check that we don't do weird lazy overriding of __call__ which break body-removal
 +        return make_conditional(
 +            response, bundle.last_modified, bundle.checksum, max_age=60*60*24)
  
 -        content = re.sub(re.compile("(@charset.+;$)", re.M), push, content)
 -        content = re.sub(re.compile("(@import.+;$)", re.M), push, content)
 +    @http.route('/web/css/<xmlid>', type='http', auth='public')
 +    def css_bundle(self, xmlid, **kw):
 +        values = {'manifest_list': manifest_list} # manifest backward compatible mode, to be removed
 +        try:
 +            assets_html = request.render(xmlid, lazy=False, qcontext=values)
 +        except QWebTemplateNotFound:
 +            return request.not_found()
 +        bundle = AssetsBundle(xmlid, assets_html, debug=request.debug)
  
 -        matches.append(content)
 -        content = '\n'.join(matches)
 +        response = request.make_response(
 +            bundle.css(), [('Content-Type', 'text/css')])
  
          return make_conditional(
 -            request.make_response(content, [('Content-Type', 'text/css')]),
 -            last_modified, checksum)
 +            response, bundle.last_modified, bundle.checksum, max_age=60*60*24)
  
 -    @http.route('/web/webclient/js', type='http', auth="none")
 -    def js(self, mods=None, db=None):
 -        files = [f[0] for f in manifest_glob('js', addons=mods, db=db)]
 -        last_modified = get_last_modified(files)
 -        if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
 -            return werkzeug.wrappers.Response(status=304)
 +class WebClient(http.Controller):
  
 -        content, checksum = concat_js(files)
 +    @http.route('/web/webclient/csslist', type='json', auth="none")
 +    def csslist(self, mods=None):
 +        return manifest_list('css', mods=mods)
  
 -        return make_conditional(
 -            request.make_response(content, [('Content-Type', 'application/javascript')]),
 -            last_modified, checksum)
 +    @http.route('/web/webclient/jslist', type='json', auth="none")
 +    def jslist(self, mods=None):
 +        return manifest_list('js', mods=mods)
  
      @http.route('/web/webclient/qweb', type='http', auth="none")
      def qweb(self, mods=None, db=None):
  .openerp .oe_application .oe_form_sheet .oe_notebook_page {
    padding: 0 16px;
  }
- .openerp .oe_form > :not(.oe_form_nosheet) header {
+ .openerp .oe_form > :not(.oe_form_nosheet) header, .openerp .oe_form > .oe_form_nosheet header {
    padding-left: 2px;
  }
- .openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) {
 -.openerp .oe_form > :not(.oe_form_nosheet) header ul, .openerp .oe_form > .oe_form_nosheet header ul {
++.openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu), .openerp .oe_form > .oe_form_nosheet header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) {
    display: inline-block;
    float: right;
  }
@@@ -1577,9 -1642,9 +1577,9 @@@ $sheet-padding: 16p
                  padding: 0 16px
      // }}}
      // FormView.header {{{
-     .oe_form > :not(.oe_form_nosheet) header
+     .oe_form > :not(.oe_form_nosheet) header, .oe_form > .oe_form_nosheet header
          padding-left: 2px
 -        ul
 +        ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu)
              display: inline-block
              float: right
          .oe_button
Simple merge
Simple merge
@@@ -27,25 -27,60 +27,27 @@@ from openerp.tools.translate import 
  
  
  class website_event(website_event):
 -    @http.route(['/event/add_cart'], type='http', auth="public", website=True, multilang=True)
 -    def add_cart(self, event_id, **post):
 -        user_obj = request.registry['res.users']
 -        order_line_obj = request.registry.get('sale.order.line')
 -        ticket_obj = request.registry.get('event.event.ticket')
 -        order_obj = request.registry.get('sale.order')
 -        website = request.registry['website']
 -
 -        order = website.ecommerce_get_current_order(request.cr, request.uid, context=request.context)
 -        if not order:
 -            order = website.ecommerce_get_new_order(request.cr, request.uid, context=request.context)
 -
 -        partner_id = user_obj.browse(request.cr, SUPERUSER_ID, request.uid,
 -                                     context=request.context).partner_id.id
  
 -        fields = [k for k, v in order_line_obj._columns.items()]
 -        values = order_line_obj.default_get(request.cr, SUPERUSER_ID, fields,
 -                                            context=request.context)
 +    @http.route(['/event/cart/update'], type='http', auth="public", methods=['POST'], website=True)
 +    def cart_update(self, event_id, **post):
 +        cr, uid, context = request.cr, request.uid, request.context
 +        ticket_obj = request.registry.get('event.event.ticket')
  
 -        _values = None
 +        sale = False
          for key, value in post.items():
 -            try:
 -                quantity = int(value)
 -                assert quantity > 0
 -            except:
 -                quantity = None
 -            ticket_id = key.split("-")[0] == 'ticket' and int(key.split("-")[1]) or None
 -            if not ticket_id or not quantity:
 +            quantity = int(value or "0")
 +            if not quantity:
                  continue
 -            ticket = ticket_obj.browse(request.cr, request.uid, ticket_id,
 -                                       context=request.context)
 -
 -            values['product_id'] = ticket.product_id.id
 -            values['event_id'] = ticket.event_id.id
 -            values['event_ticket_id'] = ticket.id
 -            values['product_uom_qty'] = quantity
 -            values['price_unit'] = ticket.price
 -            values['order_id'] = order.id
 -            values['name'] = "%s: %s" % (ticket.event_id.name, ticket.name)
 -
 -            # change and record value
 -            pricelist_id = order.pricelist_id and order.pricelist_id.id or False
 -            _values = order_line_obj.product_id_change(
 -                request.cr, SUPERUSER_ID, [], pricelist_id, ticket.product_id.id,
 -                partner_id=partner_id, context=request.context)['value']
 +            sale = True
 +            ticket_id = key.split("-")[0] == 'ticket' and int(key.split("-")[1]) or None
 +            ticket = ticket_obj.browse(cr, SUPERUSER_ID, ticket_id, context=context)
 +            request.website.sale_get_order(force_create=1)._cart_update(
 +                product_id=ticket.product_id.id, add_qty=quantity, context=dict(context, event_ticket_id=ticket.id))
+             if 'tax_id' in _values:
+                 _values['tax_id'] = [(6, 0, _values['tax_id'])]
 -            _values.update(values)
 -
 -            order_line_id = order_line_obj.create(request.cr, SUPERUSER_ID, _values, context=request.context)
 -            order_obj.write(request.cr, SUPERUSER_ID, [order.id], {'order_line': [(4, order_line_id)]}, context=request.context)
  
 -        if not _values:
 -            return request.redirect("/event/%s/" % event_id)
 +        if not sale:
 +            return request.redirect("/event/%s" % event_id)
          return request.redirect("/shop/checkout")
  
      def _add_event(self, event_name="New Event", context={}, **kwargs):
Simple merge
Simple merge