[MERGE]Merge trunk-website-al.

bzr revid: bth@tinyerp.com-20131015135019-hswfj4rqeqzzvkaz
This commit is contained in:
bth-openerp
2013-10-15 19:20:19 +05:30
57 changed files with 1870 additions and 519 deletions
+4 -4
View File
@@ -6,11 +6,11 @@
<field name="model">product.product</field>
<field name="inherit_id" ref="account.product_normal_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='property_account_expense']" position="after">
<field name="property_account_expense" position="after">
<label string="" colspan="2"/>
<field name="property_account_creditor_price_difference" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]" attrs="{'readonly':[('purchase_ok','=',0)]}" />
<newline/>
</xpath>
</field>
</field>
</record>
@@ -19,10 +19,10 @@
<field name="model">product.template</field>
<field name="inherit_id" ref="account.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="/form/notebook/page/field[@name='property_account_expense']" position="after">
<field name="property_account_expense" position="after">
<field name="property_account_creditor_price_difference" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]" attrs="{'readonly':[('purchase_ok','=',0)]}" />
<newline/>
</xpath>
</field>
</field>
</record>
@@ -0,0 +1,357 @@
# Danish translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-15 00:23+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Danish <da@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-15 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: account_bank_statement_extensions
#: help:account.bank.statement.line.global,name:0
msgid "Originator to Beneficiary Information"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: selection:account.bank.statement.line,state:0
msgid "Confirmed"
msgstr "Bekræftet"
#. module: account_bank_statement_extensions
#: view:account.bank.statement:0
#: view:account.bank.statement.line:0
msgid "Glob. Id"
msgstr ""
#. module: account_bank_statement_extensions
#: selection:account.bank.statement.line.global,type:0
msgid "CODA"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,parent_id:0
msgid "Parent Code"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Debit"
msgstr "Debet"
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_cancel_statement_line
#: model:ir.model,name:account_bank_statement_extensions.model_cancel_statement_line
msgid "Cancel selected statement lines"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,val_date:0
msgid "Value Date"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Group By..."
msgstr "Gruppér efter..."
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: selection:account.bank.statement.line,state:0
msgid "Draft"
msgstr "Kladde"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Statement"
msgstr "Erklæring"
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_confirm_statement_line
#: model:ir.model,name:account_bank_statement_extensions.model_confirm_statement_line
msgid "Confirm selected statement lines"
msgstr ""
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
#: model:ir.actions.report.xml,name:account_bank_statement_extensions.bank_statement_balance_report
msgid "Bank Statement Balances Report"
msgstr ""
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
msgid "Cancel Lines"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line.global:0
#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement_line_global
msgid "Batch Payment Info"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,state:0
msgid "Status"
msgstr "Status"
#. module: account_bank_statement_extensions
#: code:addons/account_bank_statement_extensions/account_bank_statement.py:129
#, python-format
msgid ""
"Delete operation not allowed. Please go to the associated bank "
"statement in order to delete and/or modify bank statement line."
msgstr ""
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "or"
msgstr "eller"
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Confirm Lines"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line.global:0
msgid "Transactions"
msgstr "Transaktioner"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,type:0
msgid "Type"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: report:bank.statement.balance.report:0
msgid "Journal"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Confirmed Statement Lines."
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Credit Transactions."
msgstr ""
#. module: account_bank_statement_extensions
#: model:ir.actions.act_window,help:account_bank_statement_extensions.action_cancel_statement_line
msgid "cancel selected statement lines."
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_number:0
msgid "Counterparty Number"
msgstr ""
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
msgid "Closing Balance"
msgstr ""
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
msgid "Date"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: field:account.bank.statement.line,globalisation_amount:0
msgid "Glob. Amount"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Debit Transactions."
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Extended Filters..."
msgstr ""
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Confirmed lines cannot be changed anymore."
msgstr ""
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
msgid "Are you sure you want to cancel the selected Bank Statement lines ?"
msgstr ""
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
msgid "Name"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,name:0
msgid "OBI"
msgstr ""
#. module: account_bank_statement_extensions
#: selection:account.bank.statement.line.global,type:0
msgid "ISO 20022"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Notes"
msgstr ""
#. module: account_bank_statement_extensions
#: selection:account.bank.statement.line.global,type:0
msgid "Manual"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Bank Transaction"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Credit"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,amount:0
msgid "Amount"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Fin.Account"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_currency:0
msgid "Counterparty Currency"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_bic:0
msgid "Counterparty BIC"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,child_ids:0
msgid "Child Codes"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Search Bank Transactions"
msgstr ""
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Are you sure you want to confirm the selected Bank Statement lines ?"
msgstr ""
#. module: account_bank_statement_extensions
#: help:account.bank.statement.line,globalisation_id:0
msgid ""
"Code to identify transactions belonging to the same globalisation level "
"within a batch payment"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Draft Statement Lines."
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Glob. Am."
msgstr ""
#. module: account_bank_statement_extensions
#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement_line
msgid "Bank Statement Line"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,code:0
msgid "Code"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_name:0
msgid "Counterparty Name"
msgstr ""
#. module: account_bank_statement_extensions
#: model:ir.model,name:account_bank_statement_extensions.model_res_partner_bank
msgid "Bank Accounts"
msgstr "Bankkonti"
#. module: account_bank_statement_extensions
#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement
msgid "Bank Statement"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Statement Line"
msgstr ""
#. module: account_bank_statement_extensions
#: sql_constraint:account.bank.statement.line.global:0
msgid "The code must be unique !"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,bank_statement_line_ids:0
#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_bank_statement_line
#: model:ir.ui.menu,name:account_bank_statement_extensions.bank_statement_line
msgid "Bank Statement Lines"
msgstr ""
#. module: account_bank_statement_extensions
#: code:addons/account_bank_statement_extensions/account_bank_statement.py:129
#, python-format
msgid "Warning!"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line.global:0
msgid "Child Batch Payments"
msgstr ""
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Cancel"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Statement Lines"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Total Amount"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,globalisation_id:0
msgid "Globalisation ID"
msgstr ""
+256
View File
@@ -0,0 +1,256 @@
# Danish translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-14 19:01+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Danish <da@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-15 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: base_gengo
#: view:res.company:0
msgid "Comments for Translator"
msgstr "Bemærkning til oversætter"
#. module: base_gengo
#: field:ir.translation,job_id:0
msgid "Gengo Job ID"
msgstr "Gengo Job ID"
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:114
#, python-format
msgid "This language is not supported by the Gengo translation services."
msgstr "Dette sprog er ikke supporteret af Gengo oversættelses service."
#. module: base_gengo
#: field:res.company,gengo_comment:0
msgid "Comments"
msgstr "Bemærkninger"
#. module: base_gengo
#: field:res.company,gengo_private_key:0
msgid "Gengo Private Key"
msgstr "Gengo Privat nøgle"
#. module: base_gengo
#: model:ir.model,name:base_gengo.model_base_gengo_translations
msgid "base.gengo.translations"
msgstr "base.gengo.translations"
#. module: base_gengo
#: help:res.company,gengo_auto_approve:0
msgid "Jobs are Automatically Approved by Gengo."
msgstr "Opgaver er automatisk godkendt af Gengo."
#. module: base_gengo
#: field:base.gengo.translations,lang_id:0
msgid "Language"
msgstr "Sprog"
#. module: base_gengo
#: field:ir.translation,gengo_comment:0
msgid "Comments & Activity Linked to Gengo"
msgstr "Bemærkninger og aktiviteter forbundet til Gengo"
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:124
#, python-format
msgid "Gengo Sync Translation (Response)"
msgstr "Gengo synk oversættelse (Svar)"
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:72
#, python-format
msgid ""
"Gengo `Public Key` or `Private Key` are missing. Enter your Gengo "
"authentication parameters under `Settings > Companies > Gengo Parameters`."
msgstr ""
"Gengo 'Offentlig nøgle' eller 'Private nøgle' mangler. Indtast din Gengo "
"godkendelses oplysninger under 'Opsætning > Firmaer > Gengo opsætning'."
#. module: base_gengo
#: selection:ir.translation,gengo_translation:0
msgid "Translation By Machine"
msgstr "Maskinel oversættelse"
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:155
#, python-format
msgid ""
"%s\n"
"\n"
"--\n"
" Commented on %s by %s."
msgstr ""
"%s\n"
"\n"
"--\n"
" Kommenteret på %s af %s."
#. module: base_gengo
#: field:ir.translation,gengo_translation:0
msgid "Gengo Translation Service Level"
msgstr "Gengo Oversættelses service niveau"
#. module: base_gengo
#: constraint:ir.translation:0
msgid ""
"The Gengo translation service selected is not supported for this language."
msgstr ""
"Den valgte Genko oversættelses service er ikke supporteret for dette sprog."
#. module: base_gengo
#: selection:ir.translation,gengo_translation:0
msgid "Standard"
msgstr "Standard"
#. module: base_gengo
#: help:ir.translation,gengo_translation:0
msgid ""
"You can select here the service level you want for an automatic translation "
"using Gengo."
msgstr ""
#. module: base_gengo
#: field:base.gengo.translations,restart_send_job:0
msgid "Restart Sending Job"
msgstr ""
#. module: base_gengo
#: view:ir.translation:0
msgid "To Approve In Gengo"
msgstr ""
#. module: base_gengo
#: view:res.company:0
msgid "Private Key"
msgstr "Privat nøgle"
#. module: base_gengo
#: view:res.company:0
msgid "Public Key"
msgstr "Offentlig nøgle"
#. module: base_gengo
#: field:res.company,gengo_public_key:0
msgid "Gengo Public Key"
msgstr "Gengo offentlig nøgle"
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:123
#, python-format
msgid "Gengo Sync Translation (Request)"
msgstr ""
#. module: base_gengo
#: view:ir.translation:0
msgid "Translations"
msgstr "Oversættelser"
#. module: base_gengo
#: field:res.company,gengo_auto_approve:0
msgid "Auto Approve Translation ?"
msgstr ""
#. module: base_gengo
#: model:ir.actions.act_window,name:base_gengo.action_wizard_base_gengo_translations
#: model:ir.ui.menu,name:base_gengo.menu_action_wizard_base_gengo_translations
msgid "Gengo: Manual Request of Translation"
msgstr ""
#. module: base_gengo
#: code:addons/base_gengo/ir_translation.py:62
#: code:addons/base_gengo/wizard/base_gengo_translations.py:109
#, python-format
msgid "Gengo Authentication Error"
msgstr ""
#. module: base_gengo
#: model:ir.model,name:base_gengo.model_res_company
msgid "Companies"
msgstr ""
#. module: base_gengo
#: view:ir.translation:0
msgid ""
"Note: If the translation state is 'In Progress', it means that the "
"translation has to be approved to be uploaded in this system. You are "
"supposed to do that directly by using your Gengo Account"
msgstr ""
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:82
#, python-format
msgid ""
"Gengo connection failed with this message:\n"
"``%s``"
msgstr ""
#. module: base_gengo
#: view:res.company:0
msgid "Gengo Parameters"
msgstr ""
#. module: base_gengo
#: view:base.gengo.translations:0
msgid "Send"
msgstr ""
#. module: base_gengo
#: selection:ir.translation,gengo_translation:0
msgid "Ultra"
msgstr ""
#. module: base_gengo
#: model:ir.model,name:base_gengo.model_ir_translation
msgid "ir.translation"
msgstr ""
#. module: base_gengo
#: view:ir.translation:0
msgid "Gengo Translation Service"
msgstr ""
#. module: base_gengo
#: selection:ir.translation,gengo_translation:0
msgid "Pro"
msgstr ""
#. module: base_gengo
#: view:base.gengo.translations:0
msgid "Gengo Request Form"
msgstr ""
#. module: base_gengo
#: code:addons/base_gengo/wizard/base_gengo_translations.py:114
#, python-format
msgid "Warning"
msgstr ""
#. module: base_gengo
#: help:res.company,gengo_comment:0
msgid ""
"This comment will be automatically be enclosed in each an every request sent "
"to Gengo"
msgstr ""
#. module: base_gengo
#: view:base.gengo.translations:0
msgid "Cancel"
msgstr ""
#. module: base_gengo
#: view:base.gengo.translations:0
msgid "or"
msgstr ""
+3 -5
View File
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-
import simplejson
import openerp
from openerp.addons.web.http import Controller, route
class ImportController(openerp.addons.web.http.Controller):
_cp_path = '/base_import'
@openerp.addons.web.http.httprequest
class ImportController(Controller):
@route('/base_import/set_file')
def set_file(self, req, file, import_id, jsonp='callback'):
import_id = int(import_id)
+3 -2
View File
@@ -224,12 +224,13 @@ class ir_import(orm.TransientModel):
headers, matches = self._match_headers(rows, fields, options)
# Match should have consumed the first row (iif headers), get
# the ``count`` next rows for preview
preview = itertools.islice(rows, count)
preview = list(itertools.islice(rows, count))
assert preview, "CSV file seems to have no content"
return {
'fields': fields,
'matches': matches or False,
'headers': headers or False,
'preview': list(preview),
'preview': preview,
}
except Exception, e:
# Due to lazy generators, UnicodeDecodeError (for
+7 -6
View File
@@ -26,9 +26,6 @@ from openerp.report.render.rml2pdf import customfonts
class base_config_settings(osv.osv_memory):
_name = 'base.config.settings'
_inherit = 'res.config.settings'
def _get_font(self, cr, uid, context=None):
return sorted(customfonts.RegisterCustomFonts())
_columns = {
'module_multi_company': fields.boolean('Manage multiple companies',
@@ -44,11 +41,11 @@ class base_config_settings(osv.osv_memory):
'module_base_import': fields.boolean("Allow users to import data from CSV files"),
'module_google_drive': fields.boolean('Attach Google documents to any record',
help="""This installs the module google_docs."""),
'font': fields.selection(_get_font, "Select Font", help="Set the font into the report header, will be used for every RML report of the user company"),
'font': fields.many2one('res.font', string="Report Font", help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
}
_defaults= {
'font': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.font or 'Helvetica',
'font': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.font.id,
}
def open_company(self, cr, uid, ids, context=None):
@@ -74,9 +71,13 @@ class base_config_settings(osv.osv_memory):
wizard = self.browse(cr, uid, ids)[0]
if wizard.font:
user = self.pool.get('res.users').browse(cr, uid, uid, context)
user.company_id.write({'font':wizard.font,'rml_header': self._change_header(user.company_id.rml_header,wizard.font), 'rml_header2': self._change_header(user.company_id.rml_header2,wizard.font), 'rml_header3': self._change_header(user.company_id.rml_header3,wizard.font)})
font_name = wizard.font.name
user.company_id.write({'font': wizard.font.id,'rml_header': self._change_header(user.company_id.rml_header,font_name), 'rml_header2': self._change_header(user.company_id.rml_header2, font_name), 'rml_header3': self._change_header(user.company_id.rml_header3, font_name)})
return {}
def act_discover_fonts(self, cr, uid, ids, context=None):
return self.pool.get("res.font").discover_fonts(cr, uid, ids, context)
# Preferences wizard for Sales & CRM.
# It is defined here because it is inherited independently in modules sale, crm,
# plugin_outlook and plugin_thunderbird.
+3 -2
View File
@@ -90,11 +90,12 @@
</div>
</div>
</group>
<group string="Report Settings">
<label for="font" string="RML font (Header/footer)" />
<group>
<label for="font" />
<div>
<div>
<field name="font" class="oe_inline"/>
<button string="(reload fonts)" name="act_discover_fonts" type="object" class="oe_link"/>
</div>
</div>
</group>
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -618,12 +618,12 @@
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Purchases" name="%(action_purchase_line_product_tree)d" type="action" groups="purchase.group_purchase_user"/>
</xpath>
<xpath expr="//field[@name='property_account_expense']" position="replace" >
<field name="property_account_expense" position="replace" >
<field name="property_account_expense" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]" attrs="{'readonly':[('purchase_ok','=',0)]}" groups="account.group_account_user"/>
</xpath>
<xpath expr="//field[@name='supplier_taxes_id']" position="replace" >
</field>
<field name='supplier_taxes_id' position="replace" >
<field name="supplier_taxes_id" colspan="2" widget="many2many_tags" attrs="{'readonly':[('purchase_ok','=',0)]}"/>
</xpath>
</field>
</field>
</record>
</data>
+4 -4
View File
@@ -17,8 +17,8 @@
I verify that the onchange was correctly triggered
-
!assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
- order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- order_line[0].price_unit == 1350.0
- order_line[0].name == u'[A8767] Apple In-Ear Headphones'
- order_line[0].price_unit == 79.0
- order_line[0].product_uom_qty == 8
- order_line[0].product_uom.id == ref('product.product_uom_unit')
@@ -35,7 +35,7 @@
I verify that the onchange was correctly triggered
-
!assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
- order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- order_line[0].price_unit == 1350.0 * 12
- order_line[0].name == u'[A8767] Apple In-Ear Headphones'
- order_line[0].price_unit == 79.0 * 12
- order_line[0].product_uom.id == ref('product.product_uom_dozen')
- order_line[0].product_uom_qty == 16
+16 -13
View File
@@ -44,8 +44,6 @@ def auth_method_public():
http.auth_methods['public'] = auth_method_public
NOPE = object()
# PIL images have a type flag, but no MIME. Reverse type flag to MIME.
PIL_MIME_MAPPING = {'PNG': 'image/png', 'JPEG': 'image/jpeg', 'GIF': 'image/gif', }
# Completely arbitrary limits
MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
class Website(openerp.addons.web.controllers.main.Home):
@@ -279,6 +277,15 @@ class Website(openerp.addons.web.controllers.main.Home):
return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
class Images(http.Controller):
def placeholder(self, response):
# file_open may return a StringIO. StringIO can be closed but are
# not context managers in Python 2 though that is fixed in 3
with contextlib.closing(openerp.tools.misc.file_open(
os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
mode='rb')) as f:
response.set_data(f.read())
return response.make_conditional(request.httprequest)
@website.route('/website/image', auth="public")
def image(self, model, id, field):
Model = request.registry[model]
@@ -293,13 +300,7 @@ class Images(http.Controller):
[('id', '=', id), ('website_published', '=', True)], context=request.context)
if not ids:
# file_open may return a StringIO. StringIO can be closed but are
# not context managers in Python 2 though that is fixed in 3
with contextlib.closing(openerp.tools.misc.file_open(
os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
mode='rb')) as f:
response.set_data(f.read())
return response
return self.placeholder(response)
concurrency = '__last_update'
[record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
@@ -314,9 +315,11 @@ class Images(http.Controller):
# just in case we have a timestamp without microseconds
response.last_modified = datetime.datetime.strptime(
record[concurrency], server_format)
# FIXME: no field in record?
if not field in record or not record[field]:
return response
if not record.get(field):
# Field does not exist on model or field set to False
# FIXME: maybe a field which does not exist should be a 404?
return self.placeholder(response)
response.set_etag(hashlib.sha1(record[field]).hexdigest())
response.make_conditional(request.httprequest)
@@ -350,7 +353,7 @@ class Images(http.Controller):
# FIXME: unknown format or not an image
image = Image.open(buf)
response.mimetype = PIL_MIME_MAPPING[image.format]
response.mimetype = Image.MIME[image.format]
w, h = image.size
max_w, max_h = fit
+3 -1
View File
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import ir_fields
import ir_ui_view
import website
import ir_rule
import ir_qweb
import test_models
-87
View File
@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
from lxml import etree, html
from openerp.osv import orm, fields
from openerp.tools import ustr
class converter(orm.Model):
_inherit = 'ir.fields.converter'
def _html_to_integer(self, cr, uid, model, column, value, context=None):
return int(value.text_content().strip()), []
def _html_to_float(self, cr, uid, model, column, value, context=None):
return float(value.text_content().strip()), []
def _html_to_passthrough(self, cr, uid, model, column, value, context=None):
return value.text_content().strip(), []
_html_to_char = _html_to_date = _html_to_datetime = _html_to_passthrough
def _html_to_text(self, cr, uid, model, column, value, context=None):
return value.text_content(), []
def _html_to_selection(self, cr, uid, model, column, value, context=None):
text = value.text_content().strip()
selection = column.reify(cr, uid, model, column, context=context)
for k, v in selection:
if isinstance(v, str):
v = ustr(v)
if text == v:
return k, []
warning = u"No value found for label %s in selection %s" % (text, selection)
# FIXME: ?
return False, [Warning(warning.encode('utf-8'))]
def _html_to_many2one(self, cr, uid, model, column, value, context=None):
matches = self.pool[column._obj].name_search(
cr, uid, name=value.text_content().strip(), context=context)
# FIXME: more than one match, error reporting
return matches[0][0], []
def _html_to_html(self, cr, uid, model, column, value, context=None):
content = []
if value.text: content.append(value.text)
content.extend(html.tostring(child)
for child in value.iterchildren(tag=etree.Element))
return '\n'.join(content), []
class test_converter(orm.Model):
_name = 'website.converter.test'
_columns = {
'char': fields.char(),
'integer': fields.integer(),
'float': fields.float(),
'numeric': fields.float(digits=(16, 2)),
'many2one': fields.many2one('website.converter.test.sub'),
'binary': fields.binary(),
'date': fields.date(),
'datetime': fields.datetime(),
'selection': fields.selection([
(1, "réponse A"),
(2, "réponse B"),
(3, "réponse C"),
(4, "réponse D"),
]),
'selection_str': fields.selection([
('A', "Qu'il n'est pas arrivé à Toronto"),
('B', "Qu'il était supposé arriver à Toronto"),
('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
('D', "La réponse D"),
], string="Lorsqu'un pancake prend l'avion à destination de Toronto et "
"qu'il fait une escale technique à St Claude, on dit:"),
'html': fields.html(),
'text': fields.text(),
}
class test_converter_sub(orm.Model):
_name = 'website.converter.test.sub'
_columns = {
'name': fields.char(),
}
+186
View File
@@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
"""
Website-context rendering needs to add some metadata to rendered fields,
as well as render a few fields differently.
Also, adds methods to convert values back to openerp models.
"""
import cStringIO
import itertools
import logging
import re
import urllib2
import werkzeug.utils
from lxml import etree, html
from PIL import Image as I
from openerp.osv import orm, fields
from openerp.tools import ustr
REMOTE_CONNECTION_TIMEOUT = 2.5
logger = logging.getLogger(__name__)
class QWeb(orm.AbstractModel):
""" QWeb object for rendering stuff in the website context
"""
_name = 'website.qweb'
_inherit = 'ir.qweb'
def get_converter_for(self, field_type):
return self.pool.get(
'website.qweb.field.' + field_type,
self.pool['website.qweb.field'])
class Field(orm.AbstractModel):
_name = 'website.qweb.field'
_inherit = 'ir.qweb.field'
def attributes(self, cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context):
column = record._model._all_columns[field_name].column
return itertools.chain(
super(Field, self).attributes(cr, uid, field_name, record, options,
source_element, g_att, t_att,
qweb_context),
[('data-oe-translate', 1 if column.translate else 0)]
)
def value_from_string(self, value):
return value
def from_html(self, cr, uid, model, column, element, context=None):
return self.value_from_string(element.text_content().strip())
def qweb_object(self):
return self.pool['website.qweb']
class Integer(orm.AbstractModel):
_name = 'website.qweb.field.integer'
_inherit = ['website.qweb.field']
value_from_string = int
class Float(orm.AbstractModel):
_name = 'website.qweb.field.float'
_inherit = ['website.qweb.field', 'ir.qweb.field.float']
value_from_string = float
class Text(orm.AbstractModel):
_name = 'website.qweb.field.text'
_inherit = ['website.qweb.field', 'ir.qweb.field.text']
def from_html(self, cr, uid, model, column, element, context=None):
return element.text_content()
class Selection(orm.AbstractModel):
_name = 'website.qweb.field.selection'
_inherit = ['website.qweb.field', 'ir.qweb.field.selection']
def from_html(self, cr, uid, model, column, element, context=None):
value = element.text_content().strip()
selection = column.reify(cr, uid, model, column, context=context)
for k, v in selection:
if isinstance(v, str):
v = ustr(v)
if value == v:
return k
raise ValueError(u"No value found for label %s in selection %s" % (
value, selection))
class ManyToOne(orm.AbstractModel):
_name = 'website.qweb.field.many2one'
_inherit = ['website.qweb.field', 'ir.qweb.field.many2one']
def from_html(self, cr, uid, model, column, element, context=None):
# FIXME: this behavior is really weird, what if the user wanted to edit the name of the related thingy? Should m2os really be editable without a widget?
matches = self.pool[column._obj].name_search(
cr, uid, name=element.text_content().strip(), context=context)
# FIXME: no match? More than 1 match?
assert len(matches) == 1
return matches[0][0]
class HTML(orm.AbstractModel):
_name = 'website.qweb.field.html'
_inherit = ['website.qweb.field', 'ir.qweb.field.html']
def from_html(self, cr, uid, model, column, element, context=None):
content = []
if element.text: content.append(element.text)
content.extend(html.tostring(child)
for child in element.iterchildren(tag=etree.Element))
return '\n'.join(content)
class Image(orm.AbstractModel):
"""
Widget options:
``class``
set as attribute on the generated <img> tag
"""
_name = 'website.qweb.field.image'
_inherit = ['website.qweb.field', 'ir.qweb.field.image']
def to_html(self, cr, uid, field_name, record, options,
source_element, t_att, g_att, qweb_context):
assert source_element.nodeName != 'img',\
"Oddly enough, the root tag of an image field can not be img. " \
"That is because the image goes into the tag, or it gets the " \
"hose again."
return super(Image, self).to_html(
cr, uid, field_name, record, options,
source_element, t_att, g_att, qweb_context)
def record_to_html(self, cr, uid, field_name, record, column, options=None):
cls = ''
if 'class' in options:
cls = ' class="%s"' % werkzeug.utils.escape(options['class'])
return '<img%s src="/website/image?model=%s&field=%s&id=%s"/>' % (
cls, record._model._name, field_name, record.id)
def from_html(self, cr, uid, model, column, element, context=None):
url = element.find('img').get('src')
m = re.match(r'^/website/attachment/(\d+)$', url)
if m:
attachment = self.pool['ir.attachment'].browse(
cr, uid, int(m.group(1)), context=context)
return attachment.datas
# remote URL?
try:
# should probably remove remote URLs entirely:
# * in fields, downloading them without blowing up the server is a
# challenge
# * in views, may trigger mixed content warnings if HTTPS CMS
# linking to HTTP images
# implement drag & drop image upload to mitigate?
req = urllib2.urlopen(url, timeout=REMOTE_CONNECTION_TIMEOUT)
# PIL needs a seekable file-like image, urllib result is not seekable
image = I.open(cStringIO.StringIO(req.read()))
# force a complete load of the image data to validate it
image.load()
except Exception:
logger.exception("Failed to load remote image %r", url)
return False
# don't use original data in case weird stuff was smuggled in, with
# luck PIL will remove some of it?
out = cStringIO.StringIO()
image.save(out, image.format)
return out.getvalue().encode('base64')
class Monetary(orm.AbstractModel):
_name = 'website.qweb.field.monetary'
_inherit = ['website.qweb.field', 'ir.qweb.field.monetary']
def from_html(self, cr, uid, model, column, element, context=None):
return float(element.find('span').text)
+6 -5
View File
@@ -10,6 +10,9 @@ class view(osv.osv):
'inherit_option_id': fields.many2one('ir.ui.view','Optional Inheritancy'),
'inherited_option_ids': fields.one2many('ir.ui.view','inherit_option_id','Optional Inheritancies'),
'page': fields.boolean("Whether this view is a web page template (complete)"),
'website_meta_title': fields.char("Website meta title", size=70, translate=True),
'website_meta_description': fields.text("Website meta description", translate=True),
'website_meta_keywords': fields.char("Website meta keywords", size=70, translate=True),
}
_defaults = {
'page': False,
@@ -60,11 +63,9 @@ class view(osv.osv):
field = el.get('data-oe-field')
column = Model._all_columns[field].column
convert = self.pool['ir.fields.converter'].to_field(
cr, uid, Model, column, fromtype="html", context=context)
value, warnings = convert(el)
# FIXME: report error
if warnings: return
converter = self.pool['website.qweb'].get_converter_for(
el.get('data-oe-type'))
value = converter.from_html(cr, uid, Model, column, el)
Model.write(cr, uid, [int(el.get('data-oe-id'))], {
field: value
+39
View File
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from openerp.osv import orm, fields
class test_converter(orm.Model):
_name = 'website.converter.test'
_columns = {
'char': fields.char(),
'integer': fields.integer(),
'float': fields.float(),
'numeric': fields.float(digits=(16, 2)),
'many2one': fields.many2one('website.converter.test.sub'),
'binary': fields.binary(),
'date': fields.date(),
'datetime': fields.datetime(),
'selection': fields.selection([
(1, "réponse A"),
(2, "réponse B"),
(3, "réponse C"),
(4, "réponse D"),
]),
'selection_str': fields.selection([
('A', "Qu'il n'est pas arrivé à Toronto"),
('B', "Qu'il était supposé arriver à Toronto"),
('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
('D', "La réponse D"),
], string="Lorsqu'un pancake prend l'avion à destination de Toronto et "
"qu'il fait une escale technique à St Claude, on dit:"),
'html': fields.html(),
'text': fields.text(),
}
class test_converter_sub(orm.Model):
_name = 'website.converter.test.sub'
_columns = {
'name': fields.char(),
}
+24 -5
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import fnmatch
import functools
import simplejson
@@ -49,7 +50,7 @@ def auth_method_public():
request.uid = request.session.uid
http.auth_methods['public'] = auth_method_public
def url_for(path, lang=None):
def url_for(path, lang=None, keep_query=None):
if request:
path = urljoin(request.httprequest.path, path)
langs = request.context.get('langs')
@@ -61,6 +62,16 @@ def url_for(path, lang=None):
else:
ps.insert(1, lang)
path = '/'.join(ps)
if keep_query:
keep = []
params = werkzeug.url_decode(request.httprequest.query_string)
params_keys = tuple(params.keys())
for kq in keep_query:
keep += fnmatch.filter(params_keys, kq)
if keep:
params = dict([(k, params[k]) for k in keep])
path += u'?%s' % werkzeug.urls.url_encode(params)
return path
def urlplus(url, params):
@@ -157,21 +168,29 @@ class website(osv.osv):
inherit_branding=qweb_context.setdefault('editable', False),
)
view_ref = None
# check if xmlid of the template exists
try:
module, xmlid = template.split('.', 1)
IMD.get_object_reference(cr, uid, module, xmlid)
view_ref = IMD.get_object_reference(cr, uid, module, xmlid)
except ValueError: # catches both unpack errors and gor errors
module, xmlid = 'website', template
try:
IMD.get_object_reference(cr, uid, module, xmlid)
view_ref = IMD.get_object_reference(cr, uid, module, xmlid)
except ValueError:
logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc())
return self.render(cr, uid, ids, 'website.404', qweb_context)
try:
return view.render(cr, uid, "%s.%s" % (module, xmlid),
qweb_context, context=context)
main_object = request.registry[view_ref[0]].browse(cr, uid, view_ref[1])
qweb_context['main_object'] = main_object
except Exception:
pass
try:
return view.render(
cr, uid, "%s.%s" % (module, xmlid), qweb_context,
engine='website.qweb', context=context)
except (AccessError, AccessDenied), err:
logger.error(err)
qweb_context['error'] = err[1]
+12 -1
View File
@@ -1,3 +1,14 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_website_public,website,website.model_website,base.group_public,1,0,0,0
access_website,website,website.model_website,base.group_user,1,0,0,0
access_website,website,website.model_website,base.group_user,1,0,0,0
access_website_converter_test,access_website_converter_test,model_website_converter_test,,1,1,1,1
access_website_converter_test_sub,access_website_converter_test_sub,model_website_converter_test_sub,,1,1,1,1
access_website_qweb,access_website_qweb,model_website_qweb,,0,0,0,0
access_website_qweb_field,access_website_qweb_field,model_website_qweb_field,,0,0,0,0
access_website_qweb_field_float,access_website_qweb_field_float,model_website_qweb_field_float,,0,0,0,0
access_website_qweb_field_text,access_website_qweb_field_text,model_website_qweb_field_text,,0,0,0,0
access_website_qweb_field_selection,access_website_qweb_field_selection,model_website_qweb_field_selection,,0,0,0,0
access_website_qweb_field_many2one,access_website_qweb_field_many2one,model_website_qweb_field_many2one,,0,0,0,0
access_website_qweb_field_html,access_website_qweb_field_html,model_website_qweb_field_html,,0,0,0,0
access_website_qweb_field_image,access_website_qweb_field_image,model_website_qweb_field_image,,0,0,0,0
access_website_qweb_field_monetary,access_website_qweb_field_monetary,model_website_qweb_field_monetary,,0,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_website_public website website.model_website base.group_public 1 0 0 0
3 access_website website website.model_website base.group_user 1 0 0 0
4 access_website_converter_test access_website_converter_test model_website_converter_test 1 1 1 1
5 access_website_converter_test_sub access_website_converter_test_sub model_website_converter_test_sub 1 1 1 1
6 access_website_qweb access_website_qweb model_website_qweb 0 0 0 0
7 access_website_qweb_field access_website_qweb_field model_website_qweb_field 0 0 0 0
8 access_website_qweb_field_float access_website_qweb_field_float model_website_qweb_field_float 0 0 0 0
9 access_website_qweb_field_text access_website_qweb_field_text model_website_qweb_field_text 0 0 0 0
10 access_website_qweb_field_selection access_website_qweb_field_selection model_website_qweb_field_selection 0 0 0 0
11 access_website_qweb_field_many2one access_website_qweb_field_many2one model_website_qweb_field_many2one 0 0 0 0
12 access_website_qweb_field_html access_website_qweb_field_html model_website_qweb_field_html 0 0 0 0
13 access_website_qweb_field_image access_website_qweb_field_image model_website_qweb_field_image 0 0 0 0
14 access_website_qweb_field_monetary access_website_qweb_field_monetary model_website_qweb_field_monetary 0 0 0 0
@@ -580,8 +580,10 @@
// Prevent the browser opening read-only links. (#6032)
this.attachListener( this, 'click', function( ev ) {
ev = ev.data;
var target = ev.getTarget();
if ( target.is( 'a' ) && ev.$.button != 2 && target.isReadOnly() )
var target = ev.getTarget(),
link = new CKEDITOR.dom.elementPath( target ).contains( 'a' );
if ( link && ev.$.button != 2 && link.isReadOnly() )
ev.preventDefault();
} );
+9
View File
@@ -155,10 +155,19 @@ table.editorbar-panel td.selected {
margin-bottom: 0.5em;
}
.cke_widget_wrapper {
position: static !important;
}
.cke_widget_drag_handler_container {
display: none !important;
}
.cke_widget_editable:empty:after {
content: " ";
white-space: pre-wrap;
}
/* ---- SNIPPET EDITOR ---- */
#oe_snippets {
position: fixed;
@@ -138,9 +138,18 @@ table.editorbar-panel
.modal .image-preview
margin-bottom: 0.5em
// wrapper positioned relatively for drag&drop widget which is disabled below.
// Breaks completely horribly crazy products listing page, so take it out.
.cke_widget_wrapper
position: static !important
// "remove" drag & drop of CKE widgets
.cke_widget_drag_handler_container
display: none !important
// prevent inline widgets from entirely disappearing when their (textual)
// content is removed
.cke_widget_editable:empty:after
content: " "
white-space: pre-wrap
/* ---- SNIPPET EDITOR ---- */
+18 -4
View File
@@ -160,9 +160,17 @@
editables: { text: '*' },
upcast: function (el) {
return el.attributes['data-oe-type'];
return el.attributes['data-oe-type']
&& el.attributes['data-oe-type'] !== 'monetary';
},
});
editor.widgets.add('monetary', {
editables: { text: 'span.oe_currency_value' },
upcast: function (el) {
return el.attributes['data-oe-type'] === 'monetary';
}
});
}
});
@@ -345,12 +353,17 @@
var self = this;
// setup dirty-marking for each editable element
this.fetch_editables(root)
.prop('contentEditable', true)
.addClass('oe_editable')
.each(function () {
var node = this;
observer.observe(node, OBSERVER_CONFIG);
var $node = $(node);
// only explicitly set contenteditable on view sections,
// cke widgets system will do the widgets themselves
if ($node.data('oe-model') === 'ir.ui.view') {
node.contentEditable = true;
}
observer.observe(node, OBSERVER_CONFIG);
$node.one('content_changed', function () {
$node.addClass('oe_dirty');
self.trigger('change');
@@ -470,7 +483,7 @@
},
start: function () {
var sup = this._super();
this.$el.modal();
this.$el.modal({backdrop: 'static'});
return sup;
},
save: function () {
@@ -812,6 +825,7 @@
var style = data.style;
element.setAttribute('src', data.url);
element.removeAttribute('data-cke-saved-src');
$(element.$).removeClass(this.image_styles.join(' '));
if (style) { element.addClass(style); }
},
+7 -3
View File
@@ -109,9 +109,13 @@
website.ready = function() {
if (!all_ready) {
var tpl = website.load_templates(templates);
// var session;
// var trads = openerp._t.database.load_translations(session, ['website'], website.get_context().lang);
all_ready = $.when(dom_ready, tpl);
all_ready = dom_ready.then(function () {
if ($('html').data('editable')) {
website.session = new openerp.Session();
var modules = ['website'];
return openerp._t.database.load_translations(website.session, modules, website.get_context().lang);
}
}).then(tpl).promise();
}
return all_ready;
};
+65 -14
View File
@@ -1,6 +1,6 @@
(function () {
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.seo.xml');
@@ -9,7 +9,7 @@
'click a[data-action=promote-current-page]': 'launchSeo',
}),
launchSeo: function () {
(new website.seo.Configurator()).appendTo($(document.body));
(new website.seo.Configurator(this)).appendTo($(document.body));
},
});
@@ -241,24 +241,29 @@
return hashIndex >= 0 ? url.substring(0, hashIndex) : url;
},
title: function () {
return $('title').text().trim();
return ($('title').length > 0) && $('title').text() && $('title').text().trim();
},
changeTitle: function (title) {
// TODO create tag if missing
$('title').text(title);
this.trigger('title-changed', title);
},
description: function () {
return $('meta[name=description]').attr('value').trim();
var $description = $('meta[name=description]');
return ($description.length > 0) && $description.attr('value') && $description.attr('value').trim();
},
changeDescription: function (description) {
// TODO create tag if missing
$('meta[name=description]').attr('value', description);
this.trigger('description-changed', description);
},
keywords: function () {
var parsed = $('meta[name=keywords]').attr('value').split(",");
return parsed[0] ? parsed: [];
var $keywords = $('meta[name=keywords]');
var parsed = ($keywords.length > 0) && $keywords.attr('value') && $keywords.attr('value').split(",");
return (parsed && parsed[0]) ? parsed: [];
},
changeKeywords: function (keywords) {
// TODO create tag if missing
$('meta[name=keywords]').attr('value', keywords.join(","));
this.trigger('keywords-changed', keywords);
},
@@ -364,7 +369,8 @@
}
var htmlPage = this.htmlPage;
// desactivated as too complex for end-users
// Add message suggestions at the top of the dialog
// if necessary....
// if (htmlPage.headers('h1').length === 0) {
// tips.push({
// type: 'warning',
@@ -390,15 +396,60 @@
$input.val("");
},
update: function () {
var self = this;
var data = {
title: this.htmlPage.title(),
description: this.htmlPage.description(),
keywords: this.keywordList.keywords(),
images: this.imageList.images(),
website_meta_title: self.htmlPage.title(),
website_meta_description: self.htmlPage.description(),
website_meta_keywords: self.keywordList.keywords().join(", "),
};
console.log(data);
// TODO Persist changes
this.$el.modal('hide');
self.saveMetaData(data).then(function () {
self.$el.modal('hide');
});
},
getMainObject: function () {
var repr = $('html').data('main-object');
var m = repr.match(/.+\((.+), (\d+)\)/);
if (!m) {
return null;
} else {
return {
model: m[1],
id: m[2]|0
};
}
},
loadMetaData: function () {
var self = this;
var obj = this.getMainObject();
var def = $.Deferred();
if (!obj) {
// return $.Deferred().reject(new Error("No main_object was found."));
def.resolve(null);
} else {
var fields = ['website_meta_title', 'website_meta_description', 'website_meta_keywords'];
var model = website.session.model(obj.model);
model.call('read', [[obj.id], fields, website.get_context()]).then(function (data) {
if (data.length) {
var meta = data[0];
meta.model = obj.model;
def.resolve(meta);
} else {
def.resolve(null);
}
}).fail(function () {
def.reject();
});
}
return def;
},
saveMetaData: function (data) {
var obj = this.getMainObject();
if (!obj) {
return $.Deferred().reject();
} else {
var model = website.session.model(obj.model);
return model.call('write', [[obj.id], data, website.get_context()]);
}
},
titleChanged: function () {
var self = this;
@@ -1029,6 +1029,10 @@
self.$target.carousel();
});
this.$target.on('dblclick', '.item.active .carousel-image img', function (event) {
$('#oe_rte_toolbar .cke_button__image .cke_button__image_icon').click();
});
this.rebind_event();
},
// rebind event to active carousel on edit mode
+2 -2
View File
@@ -124,7 +124,7 @@
},
{
stepId: 'drag-banner',
element: '#website-top-navbar [data-snippet-id=carousel]',
element: '#website-top-navbar [data-snippet-id=carousel].ui-draggable',
placement: 'bottom',
title: "Drag & Drop a Banner",
content: "Drag the <em>Banner</em> block and drop it in your page. <p class='text-muted'>Tip: release the mouse button when you are in a valid zone, with a preview of the banner.</p>",
@@ -238,7 +238,7 @@
$menuItem.click(function () {
tutorial.reset();
tutorial.start();
})
});
menu.append($menuItem);
});
var url = new website.UrlParser(window.location.href);
+9 -23
View File
@@ -1,31 +1,19 @@
# -*- coding: utf-8 -*-
from collections import namedtuple
from functools import partial
from xml.dom.minidom import getDOMImplementation
from lxml import etree, html
from lxml import html
from openerp.tests import common
from openerp.tools import qweb
import unittest2
from openerp.addons.base.ir import ir_qweb
renderer = qweb.QWebXml()
impl = getDOMImplementation()
document = impl.createDocument(None, None, None)
Request = namedtuple('Request', 'cr uid registry')
class RegistryProxy(object):
def __init__(self, func):
self.func = func
def __getitem__(self, name):
return self.func(name)
class TestConvertBack(common.TransactionCase):
def setUp(self):
super(TestConvertBack, self).setUp()
self.Converter = self.registry('ir.fields.converter')
def field_rountrip_result(self, field, value, expected):
model = 'website.converter.test'
Model = self.registry(model)
@@ -39,21 +27,19 @@ class TestConvertBack(common.TransactionCase):
field_value = 'record.%s' % field
e.setAttribute('t-field', field_value)
rendered = renderer.render_tag_field(
e, {'field': field_value}, '', {
rendered = self.registry('website.qweb').render_tag_field(
e, {'field': field_value}, '', ir_qweb.QWebContext({
'record': record,
'_request': Request(self.cr, self.uid, RegistryProxy( self.registry)),
})
}))
element = html.fromstring(
rendered, parser=html.HTMLParser(encoding='utf-8'))
column = Model._all_columns[field].column
converter = self.registry('website.qweb').get_converter_for(
element.get('data-oe-type'))
from_html = self.Converter.to_field(
self.cr, self.uid, model, column, 'html')
value_back, warnings = from_html(element)
self.assertEqual(warnings, [])
value_back = converter.from_html(
self.cr, self.uid, model, column, element)
if isinstance(expected, str):
expected = expected.decode('utf-8')
+15 -12
View File
@@ -31,8 +31,8 @@ class TestViewSaving(common.TransactionCase):
h.H3("Column 2"),
h.UL(
h.LI("Item 1"),
h.LI(h.SPAN("My Company", attrs(model='res.company', id=1, field='name'))),
h.LI(h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone')))
h.LI(h.SPAN("My Company", attrs(model='res.company', id=1, field='name', type='char'))),
h.LI(h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone', type='char')))
))
)
self.view_id = self.registry('ir.ui.view').create(self.cr, self.uid, {
@@ -46,14 +46,15 @@ class TestViewSaving(common.TransactionCase):
self.cr, self.uid, self.arch, context=None)
expect = [
h.SPAN("My Company", attrs(model='res.company', id=1, field='name')),
h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone')),
h.SPAN("My Company", attrs(model='res.company', id=1, field='name', type='char')),
h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone', type='char')),
]
for actual, expected in itertools.izip_longest(fields, expect):
self.eq(actual, expected)
def test_embedded_save(self):
embedded = h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone'))
embedded = h.SPAN("+00 00 000 00 0 000", attrs(
model='res.company', id=1, field='phone', type='char'))
self.registry('ir.ui.view').save_embedded_field(self.cr, self.uid, embedded)
@@ -118,8 +119,8 @@ class TestViewSaving(common.TransactionCase):
h.H3("Column 2"),
h.UL(
h.LI("Item 1"),
h.LI(h.SPAN("My Company", attrs(model='res.company', id=1, field='name'))),
h.LI(h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone')))
h.LI(h.SPAN("My Company", attrs(model='res.company', id=1, field='name', type='char'))),
h.LI(h.SPAN("+00 00 000 00 0 000", attrs(model='res.company', id=1, field='phone', type='char')))
))
))
@@ -137,8 +138,8 @@ class TestViewSaving(common.TransactionCase):
h.H3("Column 2"),
h.UL(
h.LI("wob wob wob"),
h.LI(h.SPAN("Acme Corporation", attrs(model='res.company', id=1, field='name', expression="bob"))),
h.LI(h.SPAN("+12 3456789", attrs(model='res.company', id=1, field='phone', expression="edmund"))),
h.LI(h.SPAN("Acme Corporation", attrs(model='res.company', id=1, field='name', expression="bob", type='char'))),
h.LI(h.SPAN("+12 3456789", attrs(model='res.company', id=1, field='phone', expression="edmund", type='char'))),
)
), encoding='utf-8')
View.save(self.cr, self.uid, res_id=self.view_id, value=replacement,
@@ -173,7 +174,7 @@ class TestViewSaving(common.TransactionCase):
node = html.tostring(h.SPAN(
"Acme Corporation",
attrs(model='res.company', id=company_id, field="name", expression='bob')))
attrs(model='res.company', id=company_id, field="name", expression='bob', type='char')))
self.registry('ir.ui.view').save(self.cr, self.uid, res_id=company_id,value=node)
@@ -184,7 +185,9 @@ class TestViewSaving(common.TransactionCase):
def test_field_tail(self):
View = self.registry('ir.ui.view')
replacement = ET.tostring(
h.LI(h.SPAN("+12 3456789", attrs(model='res.company', id=1, field='phone', expression="edmund")),
h.LI(h.SPAN("+12 3456789", attrs(
model='res.company', id=1, type='char',
field='phone', expression="edmund")),
"whop whop"
), encoding="utf-8")
View.save(self.cr, self.uid, res_id = self.view_id, value=replacement,
@@ -203,7 +206,7 @@ class TestViewSaving(common.TransactionCase):
h.H3("Column 2"),
h.UL(
h.LI("Item 1"),
h.LI(h.SPAN("My Company", attrs(model='res.company', id=1, field='name'))),
h.LI(h.SPAN("My Company", attrs(model='res.company', id=1, field='name', type='char'))),
h.LI(h.SPAN({'t-field': "edmund"}), "whop whop"),
))
)
+2 -2
View File
@@ -69,8 +69,8 @@
</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="container" contenteditable="true">
<div class="carousel-caption content">
<div class="container">
<div class="carousel-caption content" contenteditable="true">
<h2>Your Banner Title</h2>
<h3>Click to customize this text</h3>
<p>
+18 -11
View File
@@ -13,13 +13,24 @@
<template id="layout" name="Main layout">
&lt;!DOCTYPE html&gt;
<html t-att-lang="lang.replace('_', '-')"
t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None"
t-att-data-view-xmlid="xmlid if editable else None">
t-att-data-view-xmlid="xmlid if editable else None"
t-att-data-main-object="repr(main_object) if editable else None">
<head>
<title><t t-esc="title or res_company.name"/></title>
<meta name="openerp.company" t-att-value="res_company.name" />
<meta name="description" value="" />
<meta name="keywords" value="" />
<t t-if="main_object and 'website_meta_title' in main_object">
<t t-set="title" t-value="main_object.website_meta_title"/>
</t>
<t t-if="not title and main_object and 'name' in main_object">
<t t-set="additional_title" t-value="main_object.name"/>
</t>
<t t-if="not title">
<t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t>
</t>
<title><t t-esc="title"/></title>
<meta name="openerp.company" t-att-value="res_company.name"/>
<meta name="description" t-att-value="main_object and main_object.website_meta_description or website_meta_description"/>
<meta name="keywords" t-att-value="main_object and main_object.website_meta_keywords or website_meta_keywords"/>
<!-- Load stylesheets before scripts to avoid blocking -->
<link rel='stylesheet' href='/web/static/lib/fontawesome/css/font-awesome.css'/>
@@ -48,6 +59,7 @@
<script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
<script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
@@ -410,9 +422,6 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
<template id="contactus" name="Contact us" page="True">
<t t-call="website.layout">
<t t-set="title">Contact us -
<t t-raw="res_company.name"></t>
</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
@@ -437,10 +446,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
</t>
</template>
<template id="aboutus" page="True">
<template id="aboutus" name="About us" page="True">
<t t-call="website.layout">
<t t-set="title">About <t t-raw="res_company.name"></t>
</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container mb32">
@@ -176,13 +176,12 @@
</template>
<!-- Page -->
<template id="index" name="Blogs" page="True">
<template id="index" name="Blog" page="True">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"></script>
<link rel='stylesheet' href='/website_blog/static/src/css/website_blog.css'/>
</t>
<t t-set="title">Blog</t>
<div id="wrap">
<div class="container mt16 js_website_blog">
<div class="row">
+1 -1
View File
@@ -11,7 +11,7 @@ OpenERP Contact Form
'author': 'OpenERP SA',
'depends': ['website_partner', 'crm'],
'data': [
'website_crm_data.xml',
'data/website_crm_data.xml',
'views/website_crm.xml',
],
'installable': True,
-3
View File
@@ -57,9 +57,6 @@
<template id="contactus_thanks" name="Contact us" page="True">
<t t-call="website.layout">
<t t-set="title">Contact us -
<t t-raw="res_company.name"></t>
</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
@@ -13,7 +13,7 @@
<!-- Page -->
<template id="layout" name="Partners Layout">
<t t-call="website.layout">
<t t-set="title">Partners</t>
<t t-set="additional_title">Partners</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
@@ -12,7 +12,7 @@
<!-- Page -->
<template id="layout" name="Customer References Layout">
<t t-call="website.layout">
<t t-set="title">Customer References</t>
<t t-set="additional_title">Customer References</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
+1 -8
View File
@@ -15,10 +15,6 @@
<!-- Page -->
<template id="index" name="Events" page="True">
<t t-call="website.layout">
<t t-set="head">
<t t-raw="head or ''"/>
</t>
<t t-set="title">Events</t>
<div id="wrap">
<div class="container">
<div class="oe_structure"/>
@@ -122,10 +118,7 @@
<template id="event_description_full">
<t t-call="website.layout">
<t t-set="head">
<t t-raw="head or ''"/>
</t>
<t t-set="title">Events</t>
<t t-set="additional_title">Events</t>
<div id="wrap">
<div class="container">
+1 -1
View File
@@ -1,2 +1,2 @@
import controllers
import hr
import models
+3 -3
View File
@@ -1,5 +1,5 @@
{
'name': 'Team Page',
'name': 'Team Page',
'category': 'Website',
'summary': 'Present Your Team',
'version': '1.0',
@@ -11,10 +11,10 @@ Our Team Page
'author': 'OpenERP SA',
'depends': ['website', 'hr'],
'demo': [
'website_hr_demo.xml',
'data/website_hr_demo.xml',
],
'data': [
'website_hr_data.xml',
'data/website_hr_data.xml',
'views/website_hr.xml',
'security/ir.model.access.csv',
'security/website_hr.xml',
+1
View File
@@ -0,0 +1 @@
import hr
+4 -2
View File
@@ -11,12 +11,14 @@ OpenERP Contact Form
'author': 'OpenERP SA',
'depends': ['hr_recruitment', 'website_mail'],
'data': [
'website_hr_recruitment_demo.xml',
'website_hr_recruitment_data.xml',
'data/website_hr_recruitment_data.xml',
'views/website_hr_recruitment.xml',
'security/ir.model.access.csv',
'security/website_hr_recruitment_security.xml',
],
'demo': [
'data/website_hr_recruitment_demo.xml',
],
'css': [
'static/src/css/*.css'
],
@@ -16,12 +16,8 @@
</xpath>
</template>
<template id="index" name="Departments" page="True">
<template id="index" name="Jobs" page="True">
<t t-call="website.layout">
<t t-set="head">
<t t-raw="head or ''"/>
</t>
<t t-set="title">Jobs</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container oe_website_jobs">
@@ -64,7 +60,7 @@
<script type="text/javascript" src="/website_hr_recruitment/static/src/js/recruitment.js"></script>
<t t-raw="head or ''"/>
</t>
<t t-set="title">Job Detail</t>
<t t-set="additional_title">Job Detail</t>
<div id="wrap">
<div class="container oe_website_hr_recruitment">
<div class="row">
@@ -110,7 +106,7 @@
<template id="applyjobpost">
<t t-call="website.layout">
<t t-set="title">Apply Job</t>
<t t-set="additional_title">Apply Job</t>
<div id="wrap">
<div class="container">
<h1 class="text-center">Apply for <span t-field="job.name"></span></h1>
@@ -158,7 +154,7 @@
</template>
<template id="thankyou">
<t t-call="website.layout">
<t t-set="title">Thank You!</t>
<t t-set="additional_title">Thank You!</t>
<div id="wrap">
<div class="container">
<h1>You have successfully applied for job.</h1>
@@ -12,7 +12,7 @@
<!-- Page -->
<template id="layout" name="Members Layout">
<t t-call="website.layout">
<t t-set="title">Associations</t>
<t t-set="additional_title">Associations</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
@@ -12,7 +12,7 @@
<!-- Task -->
<template id="task" name="Task">
<t t-call="website.layout">
<t t-set="title">Task</t>
<t t-set="additional_title">Task</t>
<div id="wrap">
<div class="container">
<h4 t-field="task.name"/>
@@ -41,7 +41,7 @@
</template>
<template id="index" name="Project">
<t t-call="website.layout">
<t t-set="title">Project</t>
<t t-set="additional_title">Project</t>
<div id="wrap">
<div class="container">
<h4 t-field="project.name"/>
+1
View File
@@ -13,6 +13,7 @@ OpenERP E-Commerce
'data': [
'website_sale_data.xml',
'views/website_sale.xml',
'views/website_sale_backend.xml',
'security/ir.model.access.csv',
'security/website_sale.xml',
],
+76 -17
View File
@@ -7,6 +7,8 @@ from openerp.addons.web.http import request
from openerp.addons.website.models import website
import random
import uuid
import urllib
import simplejson
def get_order(order_id=None):
order_obj = request.registry.get('sale.order')
@@ -60,6 +62,11 @@ class Ecommerce(http.Controller):
_order = 'website_sequence desc, website_published desc'
def get_attribute_ids(self):
attributes_obj = request.registry.get('product.attribute')
attributes_ids = attributes_obj.search(request.cr, request.uid, [(1, "=", 1)], context=request.context)
return attributes_obj.browse(request.cr, request.uid, attributes_ids, context=request.context)
def get_categories(self):
domain = [('parent_id', '=', False)]
@@ -214,15 +221,63 @@ class Ecommerce(http.Controller):
product_ids = [id for id in product_ids if id in product_obj.search(request.cr, request.uid, [("id", 'in', product_ids)], context=request.context)]
return product_obj.browse(request.cr, request.uid, product_ids, context=request.context)
@website.route(['/shop/', '/shop/category/<cat_id>/', '/shop/category/<cat_id>/page/<int:page>/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
def category(self, cat_id=0, page=0, **post):
def has_search_attributes(self, attribute_id, value_id=None):
if request.httprequest.args.get('attributes'):
attributes = simplejson.loads(request.httprequest.args['attributes'])
else:
attributes = []
for key_val in attributes:
if key_val[0] == attribute_id and (not value_id or value_id in key_val[1:]):
return key_val
return False
@website.route(['/shop/attributes/'], type='http', auth="public", multilang=True)
def attributes(self, **post):
attributes = []
index = []
for key, val in post.items():
cat = key.split("-")
if len(cat) < 3 or cat[2] in ('max','minmem','maxmem'):
continue
cat_id = int(cat[1])
if cat[2] == 'min':
minmem = float(post.pop("att-%s-minmem" % cat[1]))
maxmem = float(post.pop("att-%s-maxmem" % cat[1]))
_max = int(post.pop("att-%s-max" % cat[1]))
_min = int(val)
if (minmem != _min or maxmem != _max) and cat_id not in index:
attributes.append([cat_id , [_min, _max] ])
index.append(cat_id)
elif cat_id not in index:
attributes.append([ cat_id, int(cat[2]) ])
index.append(cat_id)
else:
attributes[index.index(cat_id)].append( int(cat[2]) )
post.pop(key)
return request.redirect("/shop/?attributes=%s&%s" % (simplejson.dumps(attributes).replace(" ", ""), urllib.urlencode(post)))
def attributes_to_ids(self, attributes):
obj = request.registry.get('product.attribute.product')
domain = [(1, '=', 1)]
for key_val in attributes:
domain += [("attribute_id", "=", key_val[0])]
if isinstance(key_val[1], list):
domain += [("value", ">=", key_val[1][0]), ("value", "<=", key_val[1][1])]
else:
domain += [("value_id", "in", key_val[1:])]
att_ids = obj.search(request.cr, request.uid, domain, context=request.context)
att = obj.read(request.cr, request.uid, att_ids, ["product_id"], context=request.context)
return [r["product_id"][0] for r in att]
@website.route(['/shop/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
def category(self, category=0, attributes="", page=0, **post):
if 'promo' in post:
self.change_pricelist(post.get('promo'))
product_obj = request.registry.get('product.template')
domain = [("sale_ok", "=", True)]
#domain += [('website_published', '=', True)]
if post.get("search"):
domain += ['|', '|', '|',
@@ -230,13 +285,20 @@ class Ecommerce(http.Controller):
('description', 'ilike', "%%%s%%" % post.get("search")),
('website_description', 'ilike', "%%%s%%" % post.get("search")),
('product_variant_ids.public_categ_id.name', 'ilike', "%%%s%%" % post.get("search"))]
if cat_id:
cat_id = int(cat_id)
domain += [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
if category:
cat_id = int(category)
domain = [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
if attributes:
attributes = simplejson.loads(attributes)
if attributes:
ids = self.attributes_to_ids(attributes)
domain = [('id', 'in', ids or [0] )] + domain
step = 20
product_count = len(product_obj.search(request.cr, request.uid, domain, context=request.context))
pager = request.website.pager(url="/shop/category/%s/" % cat_id, total=product_count, page=page, step=step, scope=7, url_args=post)
pager = request.website.pager(url="/shop/%s" % post, total=product_count, page=page, step=step, scope=7, url_args=post)
request.context['pricelist'] = self.get_pricelist()
@@ -250,21 +312,18 @@ class Ecommerce(http.Controller):
styles = style_obj.browse(request.cr, request.uid, style_ids, context=request.context)
values = {
'get_categories': self.get_categories,
'category_id': cat_id,
'Ecommerce': self,
'product_ids': product_ids,
'product_ids_for_holes': fill_hole,
'get_bin_packing_products': self.get_bin_packing_products,
'get_products': self.get_products,
'search': post.get("search"),
'search': post or dict(),
'pager': pager,
'styles': styles,
'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids]
'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
}
return request.website.render("website_sale.products", values)
@website.route(['/shop/product/<int:product_id>/'], type='http', auth="public", multilang=True)
def product(self, cat_id=0, product_id=0, **post):
def product(self, product_id=0, **post):
if 'promo' in post:
self.change_pricelist(post.get('promo'))
@@ -284,12 +343,12 @@ class Ecommerce(http.Controller):
product = product_obj.browse(request.cr, request.uid, product_id, context=request.context)
values = {
'category_id': post.get('category_id') and int(post.get('category_id')) or None,
'Ecommerce': self,
'category': category,
'search': post.get("search"),
'get_categories': self.get_categories,
'category_list': category_list,
'main_object': product,
'product': product,
'search': post or dict(),
}
return request.website.render("website_sale.product", values)
+3 -1
View File
@@ -1,3 +1,5 @@
import website_styles
import product
import website_sale
import product_attributes
import website_sale
import website
@@ -0,0 +1,73 @@
from openerp.osv import osv, fields
class attributes(osv.Model):
_name = "product.attribute"
def _get_float_max(self, cr, uid, ids, field_name, arg, context=None):
result = dict.fromkeys(ids, 0)
if ids:
cr.execute("""
SELECT attribute_id, MAX(value)
FROM product_attribute_product
WHERE attribute_id in (%s)
GROUP BY attribute_id
""" % ",".join(map(str, ids)))
result.update(dict(cr.fetchall()))
return result
def _get_float_min(self, cr, uid, ids, field_name, arg, context=None):
result = dict.fromkeys(ids, 0)
if ids:
cr.execute("""
SELECT attribute_id, MIN(value)
FROM product_attribute_product
WHERE attribute_id in (%s)
GROUP BY attribute_id
""" % ",".join(map(str, ids)))
result.update(dict(cr.fetchall()))
return result
_columns = {
'name': fields.char('Name', size=64, translate=True, required=True),
'type': fields.selection([('distinct', 'Distinct'), ('float', 'Float')], "Type", required=True),
'value_ids': fields.one2many('product.attribute.value', 'attribute_id', 'Values'),
'product_ids': fields.one2many('product.attribute.product', 'attribute_id', 'Products'),
'float_max': fields.function(_get_float_max, type='float', string="Max", relation='product.attribute.product',store=True),
'float_min': fields.function(_get_float_min, type='float', string="Min", relation='product.attribute.product',store=True),
}
_defaults = {
'type': 'distinct'
}
class attributes_value(osv.Model):
_name = "product.attribute.value"
_columns = {
'name': fields.char('Value', size=64, translate=True, required=True),
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True),
'product_ids': fields.one2many('product.attribute.product', 'value_id', 'Products'),
}
class attributes_product(osv.Model):
_name = "product.attribute.product"
_columns = {
'value': fields.float('Value'),
'value_id': fields.many2one('product.attribute.value', 'Distinct Value'),
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True),
'product_id': fields.many2one('product.template', 'Product', required=True),
'type': fields.related('attribute_id', 'type', type='selection',
selection=[('distinct', 'Distinct'), ('float', 'Float')], string='Type'),
}
def onchange_attribute_id(self, cr, uid, ids, attribute_id, context=None):
attribute = self.pool.get('product.attribute').browse(cr, uid, attribute_id, context=context)
return {'value': {'type': attribute.type, 'value_id': False, 'value': ''}}
class product_template(osv.Model):
_inherit = "product.template"
_columns = {
'website_attribute_ids': fields.one2many('product.attribute.product', 'product_id', 'Product Attributes'),
}
+18
View File
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from openerp.osv import orm, fields
from openerp.addons.web import http
class Website(orm.Model):
_inherit = 'website'
def _get_pricelist(self, cr, uid, ids, field_name, arg, context=None):
# FIXME: oh god kill me now
pricelist_id = http.request.httprequest.session['ecommerce_pricelist']
return dict.fromkeys(
ids, self.pool['product.pricelist'].browse(
cr, uid, pricelist_id, context=context))
_columns = {
'pricelist_id': fields.function(
_get_pricelist, type='many2one', obj='product.pricelist')
}
@@ -119,4 +119,36 @@ $(document).ready(function () {
});
});
$(".js_slider").each(function() {
var $slide = $(this);
var $slider = $('<div>'+
'<input type="hidden" name="att-'+$slide.data("id")+'-minmem" value="'+$slide.data("min")+'"/>'+
'<input type="hidden" name="att-'+$slide.data("id")+'-maxmem" value="'+$slide.data("max")+'"/>'+
'</div>');
var $min = $("<input readonly name='att-"+$slide.data("id")+"-min'/>")
.css("border", "0").css("width", "50%")
.val($slide.data("min"));
var $max = $("<input readonly name='att-"+$slide.data("id")+"-max'/>")
.css("border", "0").css("width", "50%").css("text-align", "right")
.val($slide.data("max"));
$slide.append($min);
$slide.append($max);
$slide.append($slider);
$slider.slider({
range: true,
min: +$slide.data("min"),
max: +$slide.data("max"),
values: [
$slide.data("value-min") ? +$slide.data("value-min") : +$slide.data("min"),
$slide.data("value-max") ? +$slide.data("value-max") : +$slide.data("max")
],
slide: function( event, ui ) {
$min.val( ui.values[ 0 ] );
$max.val( ui.values[ 1 ] );
}
});
$min.val( $slider.slider( "values", 0 ) );
$max.val( $slider.slider( "values", 1 ) );
});
});
+138 -114
View File
@@ -2,75 +2,6 @@
<openerp>
<data>
<record id="product_normal_form_view" model="ir.ui.view">
<field name="name">product.normal.form.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<!-- add state field in header -->
<xpath expr="//sheet" position="before">
<div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('sale_ok', '=', False)]}">
<p attrs="{'invisible': [('website_published', '=', True)]}">
This product is <b>not available</b> for public user in your ecommerce.
</p>
<p attrs="{'invisible': [('website_published', '=', False)]}">
This product is <b>available</b> for public user in your ecommerce.
</p>
<p>Website view: <field class="oe_inline" name="website_url" widget="url"/></p>
</div>
</xpath>
<group name="sale" position="inside">
<group name="website" string="Website">
<field name="website_published"/>
<field name="suggested_product_ids" widget="many2many_tags"/>
<field name="website_style_ids" widget="many2many_tags"/>
</group>
</group>
</field>
</record>
<record model="ir.ui.view" id="product_pricelist_view">
<field name="name">product.pricelist.website.form</field>
<field name="model">product.pricelist</field>
<field name="inherit_id" ref="product.product_pricelist_view"/>
<field name="arch" type="xml">
<field name="active" position="after">
<field name="code"/>
</field>
</field>
</record>
<record model="ir.ui.view" id="product_template_form_view">
<field name="name">product.template.product.website.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<!-- add state field in header -->
<xpath expr="//sheet" position="before">
<div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('sale_ok', '=', False)]}">
<p attrs="{'invisible': [('website_published', '=', True)]}">
This product is <b>not available</b> for public user in your ecommerce.
</p>
<p attrs="{'invisible': [('website_published', '=', False)]}">
This product is <b>available</b> for public user in your ecommerce.
</p>
<p>Website view: <field class="oe_inline" name="website_url" widget="url"/></p>
</div>
</xpath>
<xpath expr="//group[@string='Product Type']" position="inside">
<field name="website_published"/>
</xpath>
<xpath expr="//page[@string='Information']" position="inside">
<group colspan="4" string="Products On Ecommerce">
<field name="suggested_product_ids" widget="many2many_tags"/>
<field name="website_style_ids" widget="many2many_tags"/>
</group>
</xpath>
</field>
</record>
<!-- Layout add nav and footer -->
<template id="header" inherit_id="website.layout" name="Custom Header">
@@ -85,11 +16,12 @@
</li>
</xpath>
</template>
<!-- List of categories -->
<template id="categories_recursive">
<li t-att-class="category.id == category_id and 'active' or ''">
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/category/#{ category.id }/" t-field="category.name"></a>
<li t-att-class="category.id == search.get('category') and 'active' or ''">
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/?category=#{ category.id }" t-field="category.name" t-keep-query="search,facettes"></a>
<ul t-if="category.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
<t t-foreach="category.child_id" t-as="category">
<t t-if="category.id in categ[1] or editable">
@@ -107,22 +39,27 @@
<div class="ribbon">Promo</div>
</div>
<div class="oe_product_description">
<a t-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,facettes">
<b t-field="product.name"/>
</a>
</div>
<div class="product_price">
<b>
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
<span class="text-danger" style="text-decoration: line-through;">
<t t-esc="product.product_variant_ids[0].lst_price" /> €
</span>&amp;nbsp;
<del class="text-danger"
t-field="product.product_variant_ids[0].lst_price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>&amp;nbsp;
</t>
<t t-esc="product.product_variant_ids[0].price" /> €
<span t-field="product.product_variant_ids[0].price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</b>
</div>
<div class="oe_product_image text-center">
<a t-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,facettes">
<img class="img" t-att-src="product.img('image')"/>
</a>
</div>
@@ -135,7 +72,7 @@
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<t t-set="title">Our Products</t>
<t t-set="additional_title">Our Products</t>
<div id="wrap">
<div class="oe_structure"/>
<div class="container oe_website_sale">
@@ -143,14 +80,14 @@
<div class="col-sm-4">
<h1>Our Products</h1>
</div><div class="col-sm-2 pagination text-center">
<a t-if="editable" t-href="/shop/#{ category_id and ('category/%s/' % category_id) or ''}add_product/" class="btn btn-primary btn-default">New Product</a>
<a t-if="editable" t-href="/shop/add_product/" class="btn btn-primary btn-default" t-keep-query="category,search,facettes">New Product</a>
</div><div class="col-sm-6">
<t t-call="website.pager">
<t t-set="classname">pull-right</t>
</t>
<form t-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;">
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;" t-keep-query="category,search,facettes">
<div class="form-group">
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
</div>
</form>
</div>
@@ -160,12 +97,13 @@
<div class="col-md-12" id="products_grid">
<table width="100%">
<tbody>
<t t-set="table_products" t-value="get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
<t t-set="table_products" t-value="Ecommerce.get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
<tr t-foreach="table_products" t-as="tr_product">
<t t-foreach="tr_product" t-as="td_product">
<t t-if="td_product">
<t t-set="product" t-value="td_product['product']"/>
<td t-att-colspan="td_product['x']"
t-attf-width="#{td_product['x']*25}%"
t-att-rowspan="td_product['y']"
t-attf-class="oe_product oe-height-#{td_product['y']*2} #{ td_product['class'] }">
@@ -266,7 +204,7 @@
<template id="list_view" inherit_option_id="website_sale.products" name="List View">
<xpath expr="//div[@id='products_grid']/table" position="replace">
<div class="row">
<t t-set="products" t-value="get_products(product_ids)"/>
<t t-set="products" t-value="Ecommerce.get_products(product_ids)"/>
<t t-foreach="products" t-as="product">
<div class="col-md-12 oe_list_products oe-height-1">
<t t-call="website_sale.products_cart"/>
@@ -284,7 +222,7 @@
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
</t>
<t t-set="title" t-raw="product.name"/>
<t t-set="additional_title" t-value="product.name"/>
<div id="wrap">
<section class="container mt8">
@@ -292,7 +230,7 @@
<div class="col-sm-5">
<ol class="breadcrumb">
<li><a t-href="/shop">Products</a></li>
<li t-if="category"><a t-att-href="'/shop/category/%s' % (category_id,)"><span t-field="category.name"/></a></li>
<li t-if="search.get('category')"><a t-att-href="'/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
<li class="active"><span t-field="product.name"></span></li>
</ol>
</div><div class="col-sm-3">
@@ -308,8 +246,13 @@
</li>
</t>
</div><div class="col-sm-3 col-sm-offset-1">
<form t-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right">
<form t-action="/shop/" method="get" class="pull-right" t-keep-query="category,facettes">
<div class="input-group">
<t t-if="search">
<t foreach="search.items()" t-as="key">
<input t-att-name="key[0]" t-att-value="key[1]"/>
</t>
</t>
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
</div>
@@ -321,7 +264,7 @@
<section class="container oe_website_sale mb16" id="product_detail">
<div class="row">
<div class="col-sm-7 col-md-7 col-lg-7">
<img class="img" t-att-src="product.img('image')" style="max-height: 500px"/>
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image"}'/>
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
<h1 t-field="product.name">Product Name</h1>
@@ -332,7 +275,10 @@
<input type="radio" name="product_id" t-att-value="product.id" t-att-checked="product == product.product_variant_ids[0] or None"/>
<t t-esc="product.variants or ''">Standard</t>
<span class="badge" t-if="product.price_extra">
<t t-esc="product.price_extra > 0 and '+' or ''"/><span t-esc="product.price_extra"/> €
<t t-esc="product.price_extra > 0 and '+' or ''"/><span t-field="product.price_extra" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</span>
</label>
<br/>
@@ -343,10 +289,11 @@
<b><span class="oe_price" t-esc="product.product_variant_ids[0].price" /> €</b>
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
&amp;nbsp;&amp;nbsp;<small class="text-danger" style="text-decoration: line-through;">
<span class="text-danger" style="text-decoration: line-through;">
<t t-esc="product.product_variant_ids[0].lst_price" /> €
</small>
</span>&amp;nbsp;
</t>
<h4><b><span class="oe_price" t-esc="product.product_variant_ids[0].price" /> €</b></h4>
</h4>
</div>
<button class="btn btn-primary btn-lg mt8">Add to Cart</button>
@@ -375,7 +322,7 @@
<t t-foreach="product.recommended_products()" t-as="product">
<div class='col-md-2 thumbnail' style='width: 170px; margin-right: 16px;'>
<div class='mt16 text-center'>
<img t-att-src="product.img('image_small')"/>
<span t-field="product.image_small"/>
<h5>
<a t-href="/shop/product/#{ product.id }/"
style="display: block">
@@ -393,14 +340,13 @@
<!-- Page Shop my cart -->
<template id="mycart" name="My cart" page="True">
<template id="mycart" name="Your Cart" page="True">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<t t-set="title">Your Cart</t>
<div id="wrap">
<div class="container oe_website_sale">
@@ -435,7 +381,8 @@
<tr t-foreach="website_sale_order.order_line" t-as="line">
<td colspan="2" t-if="not line.product_id.product_tmpl_id"></td>
<td t-if="line.product_id.product_tmpl_id">
<img class="img-rounded" t-att-src="line.product_id.img('image_small')"/>
<span t-field="line.product_id.image_small"
t-field-options='{"widget": "image", "class": "img-rounded"}'/>
</td>
<td t-if="line.product_id.product_tmpl_id">
<div>
@@ -446,10 +393,16 @@
<div class="text-muted" t-field="line.product_id.description_sale"/>
</td>
<td class="text-center">
<div class="text-danger" style="text-decoration: line-through;" t-if="line.product_id.lst_price &gt; line.price_unit">
<t t-esc="line.product_id.lst_price" /> €
</div>
<span t-field="line.price_unit"></span> €
<del class="text-danger" t-if="line.product_id.lst_price &gt; line.price_unit">
<span t-field="line.product_id.lst_price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</del>
<span t-field="line.price_unit" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</td>
<td>
<div class="input-group">
@@ -479,11 +432,21 @@
<thead>
<tr style="border-top: 1px solid #000">
<th><h3>Total:</h3></th>
<th class="text-right"><h3><t t-esc="website_sale_order and website_sale_order.amount_total or 0"/> €</h3></th>
<th class="text-right"><h3>
<span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></h3>
</th>
</tr>
<tr class="text-muted">
<td><abbr title="Taxes may be updated after providing shipping address">Incl. Taxes:</abbr></td>
<td class="text-right"><t t-esc="website_sale_order and website_sale_order.amount_tax or 0"/> €</td>
<td class="text-right">
<span t-field="website_sale_order.amount_tax" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</td>
</tr>
</thead>
</table>
@@ -519,10 +482,10 @@
<template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
<xpath expr="//div[@id='products_grid']" position="before">
<div class="col-md-3">
<div id="categories" class="col-md-3">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if category_id else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="get_categories()"/>
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
<t t-foreach="categ[0]" t-as="category">
<t t-call="website_sale.categories_recursive"/>
</t>
@@ -534,6 +497,42 @@
</xpath>
</template>
<template id="products_attributes" inherit_option_id="website_sale.products_categories" name="Product Attributes">
<xpath expr="//div[@id='categories']" position="inside">
<form t-action="/shop/attributes/" method="post" t-keep-query="category,search">
<ul class="nav nav-pills nav-stacked mt16">
<t t-set="attribute_ids" t-value="Ecommerce.get_attribute_ids()"/>
<t t-foreach="attribute_ids" t-as="attribute_id">
<li t-if="attribute_id.value_ids and attribute_id.type == 'distinct'">
<div t-field="attribute_id.name"/>
<ul class="nav nav-pills nav-stacked">
<t t-foreach="attribute_id.value_ids" t-as="value_id">
<li t-att-class="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'active' or ''">
<label style="margin: 0 20px;">
<input type="checkbox" t-att-name="'att-%s-%s' % (attribute_id.id, value_id.id)"
t-att-checked="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'checked' or ''"/>
<span style="font-weight: normal" t-field="value_id.name"/>
</label>
</li>
</t>
</ul>
</li>
<li t-if="attribute_id.type == 'float' and attribute_id.float_min != attribute_id.float_max">
<div t-field="attribute_id.name"/>
<t t-set="attribute" t-value="Ecommerce.has_search_attributes(attribute_id.id)"/>
<div style="margin: 0 20px;" class="js_slider"
t-att-data-id="attribute_id.id"
t-att-data-value-min="attribute and attribute[1][0] or attribute_id.float_min"
t-att-data-value-max="attribute and attribute[1][1] or attribute_id.float_max"
t-att-data-min="attribute_id.float_min"
t-att-data-max="attribute_id.float_max"></div>
</li>
</t>
</ul>
<button class="btn btn-xs btn-primary mt16">Apply filter</button>
</form>
</xpath>
</template>
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in list view">
<xpath expr="//table[@id='mycart_products']" position="after">
@@ -554,7 +553,8 @@
<td>
<a t-href="/shop/product/#{ product.product_tmpl_id.id }/">
<img class="img-rounded" t-att-src="product.img('image_small')"/>
<span t-field="product.image_small"
t-field-options='{"widget": "image", "class": "img-rounded"}'/>
</a>
</td>
<td>
@@ -566,7 +566,10 @@
<div class="text-muted" t-field="product.description_sale"/>
</td>
<td>
<span t-field="product.lst_price"></span> €
<span t-field="product.lst_price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</td>
<td class="text-center">
<a t-href="./add_cart/?product_id=#{ product.id }"><strong>Add to Cart</strong></a>
@@ -604,7 +607,7 @@
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<t t-set="title">Shop - Checkout</t>
<t t-set="additional_title">Shop - Checkout</t>
<div id="wrap">
<div class="container oe_website_sale">
<ul class="wizard pull-right">
@@ -734,11 +737,20 @@
<h3 class="page-header mt16">Your Order <small><a href="/shop/mycart"><span class="icon-arrow-right"/> change</a></small></h3>
<div class="row">
<div class="col-sm-6 text-right">Subtotal:</div>
<div class="col-sm-6"><t t-esc="website_sale_order.amount_untaxed or 0"/> €</div>
<div class="col-sm-6"><span t-esc="website_sale_order.amount_untaxed" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></div>
<div class="col-sm-6 text-right">Taxes:</div>
<div class="col-sm-6"><t t-esc="website_sale_order.amount_tax or 0"/> €</div>
<div class="col-sm-6"><span t-field="website_sale_order.amount_tax" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></div>
<div class="col-sm-6 text-right"><h4>Total To Pay:</h4></div>
<div class="col-sm-6"><h4><t t-esc="website_sale_order.amount_total or 0"/> €</h4></div>
<div class="col-sm-6"><h4><span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></h4></div>
</div>
<button type="submit" class="btn btn-default btn-primary pull-right mt16" t-if="user_id.id != website.public_user.id">
Confirm <span class="icon-long-arrow-right"/>
@@ -759,7 +771,7 @@
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<t t-set="title">Select Payment Mode</t>
<t t-set="additional_title">Shop - Select Payment Mode</t>
<div id="wrap">
<div class="container oe_website_sale">
@@ -790,13 +802,19 @@
<tr t-foreach="website_sale_order.order_line" t-as="line">
<td colspan="2" t-if="not line.product_id.product_tmpl_id"></td>
<td t-if="line.product_id.product_tmpl_id">
<a t-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/"><img class="img-rounded" t-att-src="line.product_id.img('image_small')"/></a>
<a t-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/">
<span t-field="line.product_id.image_small"
t-field-options='{"widget": "image", "class": "img-rounded"}'/>
</a>
</td>
<td t-if="line.product_id.product_tmpl_id">
<strong t-field="line.product_id.name"/>
</td>
<td class="text-center">
<span t-field="line.price_unit"></span> €
<span t-field="line.price_unit" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</td>
<td>
<div t-esc="line.product_uom_qty"/>
@@ -812,11 +830,17 @@
<thead>
<tr style="border-top: 1px solid #000">
<th><h3>Total:</h3></th>
<th class="text-right"><h3><t t-esc="website_sale_order and website_sale_order.amount_total or 0"/> €</h3></th>
<th class="text-right"><h3><span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></h3></th>
</tr>
<tr class="text-muted">
<td><abbr title="Taxes may be updated after providing shipping address">Incl. Taxes:</abbr></td>
<td class="text-right"><t t-esc="website_sale_order and website_sale_order.amount_tax or 0"/> €</td>
<td class="text-right"><span t-field="website_sale_order.amount_tax" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></td>
</tr>
</thead>
</table>
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="product_normal_form_view" model="ir.ui.view">
<field name="name">product.normal.form.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<!-- add state field in header -->
<xpath expr="//sheet" position="before">
<div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('sale_ok', '=', False)]}">
<p attrs="{'invisible': [('website_published', '=', True)]}">
This product is <b>not available</b> for public user in your ecommerce.
</p>
<p attrs="{'invisible': [('website_published', '=', False)]}">
This product is <b>available</b> for public user in your ecommerce.
</p>
<p>Website view: <field class="oe_inline" name="website_url" widget="url"/></p>
</div>
</xpath>
<group name="sale" position="inside">
<group name="website" string="Website">
<field name="website_published"/>
<field name="suggested_product_ids" widget="many2many_tags"/>
<field name="website_style_ids" widget="many2many_tags"/>
</group>
</group>
</field>
</record>
<record model="ir.ui.view" id="product_pricelist_view">
<field name="name">product.pricelist.website.form</field>
<field name="model">product.pricelist</field>
<field name="inherit_id" ref="product.product_pricelist_view"/>
<field name="arch" type="xml">
<field name="active" position="after">
<field name="code"/>
</field>
</field>
</record>
<record model="ir.ui.view" id="product_template_form_view">
<field name="name">product.template.product.website.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<!-- add state field in header -->
<xpath expr="//sheet" position="before">
<div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('sale_ok', '=', False)]}">
<p attrs="{'invisible': [('website_published', '=', True)]}">
This product is <b>not available</b> for public user in your ecommerce.
</p>
<p attrs="{'invisible': [('website_published', '=', False)]}">
This product is <b>available</b> for public user in your ecommerce.
</p>
<p>Website view: <field class="oe_inline" name="website_url" widget="url"/></p>
</div>
</xpath>
<xpath expr="//group[@string='Product Type']" position="inside">
<field name="website_published"/>
</xpath>
<xpath expr="//page[@string='Information']" position="inside">
<group colspan="4" string="Products On Ecommerce">
<field name="suggested_product_ids" widget="many2many_tags"/>
<field name="website_style_ids" widget="many2many_tags"/>
<field colspan="4" name="website_attribute_ids" nolabel="1">
<tree string="Product Attributes" editable="bottom">
<field name="attribute_id" on_change="onchange_attribute_id(attribute_id)"/>
<field name="type" invisible="1"/>
<field name="value" attrs="{'required': [('type','=','float')]}"/>
<field name="value_id"
attrs="{'required': [('type','=','distinct')]}"
context="{'default_attribute_id': attribute_id}"
domain="[('attribute_id', '=', attribute_id)]"/>
</tree>
</field>
</group>
</xpath>
</field>
</record>
</data>
</openerp>