Merge remote-tracking branch 'odoo/master' into master-inline-searchview-ged

This commit is contained in:
Géry Debongnie 2014-05-28 10:37:16 +02:00
commit 5cc8f9ca42
117 changed files with 2181 additions and 739 deletions

2
.gitignore vendored
View File

@ -25,4 +25,4 @@ install/win32/meta.py
/lib/ /lib/
/man/ /man/
/share/ /share/
/src/ /src/

View File

@ -60,7 +60,7 @@
</group> </group>
<group> <group>
<field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '&lt;&gt;', 'view')]" name="account_id" on_change="onchange_account_id(product_id, parent.partner_id, parent.type, parent.fiscal_position,account_id)" groups="account.group_account_user"/> <field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '&lt;&gt;', 'view')]" name="account_id" on_change="onchange_account_id(product_id, parent.partner_id, parent.type, parent.fiscal_position,account_id)" groups="account.group_account_user"/>
<field name="invoice_line_tax_id" context="{'type':parent.type}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/> <field name="invoice_line_tax_id" context="{'type':parent.get('type')}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/>
<field domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id)]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/> <field domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id)]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/> <field name="company_id" groups="base.group_multi_company" readonly="1"/>
</group> </group>

View File

@ -956,7 +956,6 @@
<field name="ref_tax_sign"/> <field name="ref_tax_sign"/>
</group> </group>
<group string="Children/Sub Taxes" colspan="2"> <group string="Children/Sub Taxes" colspan="2">
<field name="child_depend" class="oe_inline"/>
<field name="child_ids" nolabel="1" colspan="2"> <field name="child_ids" nolabel="1" colspan="2">
<tree string="Account Tax"> <tree string="Account Tax">
<field name="sequence"/> <field name="sequence"/>

View File

@ -161,7 +161,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
dates_query += ' < %s)' dates_query += ' < %s)'
args_list += (form[str(i)]['stop'],) args_list += (form[str(i)]['stop'],)
args_list += (self.date_from,) args_list += (self.date_from,)
self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit) self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit), l.reconcile_partial_id
FROM account_move_line AS l, account_account, account_move am FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id=am.id) WHERE (l.account_id = account_account.id) AND (l.move_id=am.id)
AND (am.state IN %s) AND (am.state IN %s)
@ -173,12 +173,24 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
AND account_account.active AND account_account.active
AND ''' + dates_query + ''' AND ''' + dates_query + '''
AND (l.date <= %s) AND (l.date <= %s)
GROUP BY l.partner_id''', args_list) GROUP BY l.partner_id, l.reconcile_partial_id''', args_list)
t = self.cr.fetchall() partners_partial = self.cr.fetchall()
d = {} partners_amount = dict((i[0],0) for i in partners_partial)
for i in t: for partner_info in partners_partial:
d[i[0]] = i[1] if partner_info[2]:
history.append(d) # in case of partial reconciliation, we want to keep the left amount in the oldest period
self.cr.execute('''SELECT MIN(COALESCE(date_maturity,date)) FROM account_move_line WHERE reconcile_partial_id = %s''', (partner_info[2],))
date = self.cr.fetchall()
if date and args_list[-3] <= date[0][0] <= args_list[-2]:
# partial reconcilation
self.cr.execute('''SELECT SUM(l.debit-l.credit)
FROM account_move_line AS l
WHERE l.reconcile_partial_id = %s''', (partner_info[2],))
unreconciled_amount = self.cr.fetchall()
partners_amount[partner_info[0]] += unreconciled_amount[0][0]
else:
partners_amount[partner_info[0]] += partner_info[1]
history.append(partners_amount)
for partner in partners: for partner in partners:
values = {} values = {}

View File

@ -5,7 +5,7 @@
<t t-call="report.external_layout"> <t t-call="report.external_layout">
<div class="page"> <div class="page">
<div class="row"> <div class="row">
<div class="col-xs-4 col-xs-offset-8"> <div class="col-xs-5 col-xs-offset-7">
<address t-field="o.partner_id" <address t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' /> t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
<span t-field="o.partner_id.vat"/> <span t-field="o.partner_id.vat"/>

View File

@ -5,7 +5,7 @@
<t t-call="report.external_layout"> <t t-call="report.external_layout">
<div class="page"> <div class="page">
<div class="row"> <div class="row">
<div class="col-xs-4 col-xs-offset-6"> <div class="col-xs-5 col-xs-offset-7">
<span t-field="o.name"/><br/> <span t-field="o.name"/><br/>
<span t-raw="addresses[o.id].replace('\n\n', '\n').replace('\n', '&lt;br&gt;')"/> <span t-raw="addresses[o.id].replace('\n\n', '\n').replace('\n', '&lt;br&gt;')"/>
<span t-field="o.vat"/> <span t-field="o.vat"/>

View File

@ -69,7 +69,7 @@ class account_analytic_invoice_line(osv.osv):
if partner_id: if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=local_context) part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=local_context)
if part.lang: if part.lang:
context.update({'lang': part.lang}) local_context.update({'lang': part.lang})
result = {} result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) res = self.pool.get('product.product').browse(cr, uid, product, context=local_context)
@ -79,7 +79,12 @@ class account_analytic_invoice_line(osv.osv):
price = res.price price = res.price
else: else:
price = res.list_price price = res.list_price
result.update({'name': name or res.description or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price}) if not name:
name = self.pool.get('product.product').name_get(cr, uid, [res.id], context=local_context)[0][1]
if res.description_sale:
name += '\n'+res.description_sale
result.update({'name': name or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price})
res_final = {'value':result} res_final = {'value':result}
if result['uom_id'] != res.uom_id.id: if result['uom_id'] != res.uom_id.id:

View File

@ -169,7 +169,7 @@
<div attrs="{'invisible': [('recurring_invoices','=',False)]}"> <div attrs="{'invisible': [('recurring_invoices','=',False)]}">
<field name="recurring_invoice_line_ids"> <field name="recurring_invoice_line_ids">
<tree string="Account Analytic Lines" editable="bottom"> <tree string="Account Analytic Lines" editable="bottom">
<field name="product_id" on_change="product_id_change(product_id, uom_id, quantity, name, parent.partner_id, price_unit, parent.pricelist_id, parent.company_id)"/> <field name="product_id" on_change="product_id_change(product_id, uom_id, quantity, False, parent.partner_id, False, parent.pricelist_id, parent.company_id)"/>
<field name="name"/> <field name="name"/>
<field name="quantity"/> <field name="quantity"/>
<field name="uom_id"/> <field name="uom_id"/>

View File

@ -22,6 +22,7 @@
import datetime import datetime
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp.tools import ustr
from openerp.tools.translate import _ from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp import openerp.addons.decimal_precision as dp
@ -114,7 +115,7 @@ class crossovered_budget_lines(osv.osv):
for line in self.browse(cr, uid, ids, context=context): for line in self.browse(cr, uid, ids, context=context):
acc_ids = [x.id for x in line.general_budget_id.account_ids] acc_ids = [x.id for x in line.general_budget_id.account_ids]
if not acc_ids: if not acc_ids:
raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % str(line.general_budget_id.name)) raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % ustr(line.general_budget_id.name))
date_to = line.date_to date_to = line.date_to
date_from = line.date_from date_from = line.date_from
if context.has_key('wizard_date_from'): if context.has_key('wizard_date_from'):

View File

@ -7,7 +7,7 @@
<t t-call="report.external_layout"> <t t-call="report.external_layout">
<div class="page"> <div class="page">
<div class="row"> <div class="row">
<div class="col-xs-4 col-xs-offset-6"> <div class="col-xs-5 col-xs-offset-7">
<div t-field="o.partner_id" <div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/> t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
<span t-field="o.partner_id.vat"/> <span t-field="o.partner_id.vat"/>

View File

@ -3,7 +3,7 @@
======================== ========================
****** ******
saas-5 saas-4
****** ******
- Library update: ``mygengo`` (https://pypi.python.org/pypi/mygengo/1.3.3) was outdated and has been replaced by ``gengo`` (https://pypi.python.org/pypi/gengo). - Library update: ``mygengo`` (https://pypi.python.org/pypi/mygengo/1.3.3) was outdated and has been replaced by ``gengo`` (https://pypi.python.org/pypi/gengo).

View File

@ -13,7 +13,7 @@ for customization purpose.
'depends': ['web'], 'depends': ['web'],
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,
'data': [], 'data': ['views/base_import_module.xml'],
'qweb': [], 'qweb': [],
'test': [], 'test': [],
} }

View File

@ -1,14 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import functools import functools
import os
import zipfile
from os.path import join as opj
import openerp import openerp
from openerp.http import Controller, route, request, Response from openerp.http import Controller, route, request, Response
MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
def webservice(f): def webservice(f):
@functools.wraps(f) @functools.wraps(f)
def wrap(*args, **kw): def wrap(*args, **kw):
@ -42,32 +36,4 @@ class ImportModule(Controller):
@webservice @webservice
def upload(self, mod_file=None, **kw): def upload(self, mod_file=None, **kw):
self.check_user() self.check_user()
imm = request.registry['ir.module.module'] return request.registry['ir.module.module'].import_zipfile(request.cr, request.uid, mod_file, context=request.context)[0]
if not mod_file:
raise Exception("No file sent.")
if not zipfile.is_zipfile(mod_file):
raise Exception("Not a zipfile.")
success = []
errors = dict()
with zipfile.ZipFile(mod_file, "r") as z:
for zf in z.filelist:
if zf.file_size > MAX_FILE_SIZE:
raise Exception("File '%s' exceed maximum allowed file size" % zf.filename)
with openerp.tools.osutil.tempdir() as module_dir:
z.extractall(module_dir)
dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
for mod_name in dirs:
try:
# assert mod_name.startswith('theme_')
path = opj(module_dir, mod_name)
imm.import_module(request.cr, request.uid, mod_name, path, context=request.context)
success.append(mod_name)
except Exception, e:
errors[mod_name] = str(e)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
return '\n'.join(r)

View File

@ -1,2 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import ir_module import ir_module
import base_import_module

View File

@ -0,0 +1,53 @@
import base64
from StringIO import StringIO
from io import BytesIO
from openerp.osv import osv, fields
class base_import_module(osv.TransientModel):
""" Import Module """
_name = "base.import.module"
_description = "Import Module"
_columns = {
'module_file': fields.binary('Module .ZIP file', required=True),
'state':fields.selection([('init','init'),('done','done')], 'Status', readonly=True),
'import_message': fields.char('Import message'),
}
_defaults = {
'state': 'init',
}
def import_module(self, cr, uid, ids, context=None):
module_obj = self.pool.get('ir.module.module')
data = self.browse(cr, uid, ids[0] , context=context)
zip_data = base64.decodestring(data.module_file)
fp = BytesIO()
fp.write(zip_data)
res = module_obj.import_zipfile(cr, uid, fp, context=context)
self.write(cr, uid, ids, {'state': 'done', 'import_message': res[0]}, context=context)
context = dict(context, module_name=res[1])
# Return wizard otherwise it will close wizard and will not show result message to user.
return {
'name': 'Import Module',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'res_id': ids[0],
'res_model': 'base.import.module',
'type': 'ir.actions.act_window',
'context': context,
}
def action_module_open(self, cr, uid, ids, context):
return {
'domain': [('name', 'in', context.get('module_name',[]))],
'name': 'Modules',
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'ir.module.module',
'view_id': False,
'type': 'ir.actions.act_window',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,14 +1,18 @@
import logging import logging
import os import os
import sys import sys
import zipfile
from os.path import join as opj from os.path import join as opj
import openerp import openerp
from openerp.osv import osv from openerp.osv import osv
from openerp.tools import convert_file from openerp.tools import convert_file
from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
class view(osv.osv): class view(osv.osv):
_inherit = "ir.module.module" _inherit = "ir.module.module"
@ -22,7 +26,7 @@ class view(osv.osv):
unmet_dependencies = set(terp['depends']).difference(known_mods_names.keys()) unmet_dependencies = set(terp['depends']).difference(known_mods_names.keys())
if unmet_dependencies: if unmet_dependencies:
raise Exception("Unmet module dependencies: %s" % ', '.join(unmet_dependencies)) raise osv.except_osv(_('Error !'), _("Unmet module dependencies: %s" % ', '.join(unmet_dependencies)))
if mod: if mod:
self.write(cr, uid, mod.id, values) self.write(cr, uid, mod.id, values)
@ -69,3 +73,33 @@ class view(osv.osv):
return True return True
def import_zipfile(self, cr, uid, module_file, context=None):
if not module_file:
raise Exception("No file sent.")
if not zipfile.is_zipfile(module_file):
raise osv.except_osv(_('Error !'), _('File is not a zip file!'))
success = []
errors = dict()
module_names = []
with zipfile.ZipFile(module_file, "r") as z:
for zf in z.filelist:
if zf.file_size > MAX_FILE_SIZE:
raise osv.except_osv(_('Error !'), _("File '%s' exceed maximum allowed file size" % zf.filename))
with openerp.tools.osutil.tempdir() as module_dir:
z.extractall(module_dir)
dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
for mod_name in dirs:
module_names.append(mod_name)
try:
# assert mod_name.startswith('theme_')
path = opj(module_dir, mod_name)
self.import_module(cr, uid, mod_name, path, context=context)
success.append(mod_name)
except Exception, e:
errors[mod_name] = str(e)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
return '\n'.join(r), module_names

View File

@ -3,49 +3,51 @@
<data> <data>
<record id="view_base_module_import" model="ir.ui.view"> <record id="view_base_module_import" model="ir.ui.view">
<field name="name">Module Import</field> <field name="name">Import Module</field>
<field name="model">base.module.import</field> <field name="model">base.import.module</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Import module" version="7.0"> <form string="Import module" version="7.0">
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<separator string="Module Import" colspan="4"/> <separator string="Import Module" colspan="4"/>
<group states="init" col="4"> <group states="init" col="4">
<label string="Select module package to import (.zip file):" colspan="4"/> <label string="Select module package to import (.zip file):" colspan="4"/>
<field name="module_file" colspan="4"/> <field name="module_file" colspan="4"/>
</group> </group>
<group states="done" col="4"> <group states="done" col="4">
<label string="Module file successfully imported!" colspan="4"/> <field name="import_message" colspan="4" nolabel="1" readonly="1"/>
</group> </group>
<footer> <footer>
<button name="importzip" string="Import module" type="object" states="init" class="oe_highlight"/> <div states="init">
<label string="or" states="init"/> <button name="import_module" string="Import Module" type="object" class="oe_highlight"/> or
<button name="action_module_open" string="Open Modules" type="object" states="done" class="oe_highlight"/> <button special="cancel" string="Cancel" class="oe_link"/>
<label string="or" states="done"/> </div>
<button string="Cancel" class="oe_link" special="cancel"/> <div states="done">
<button name="action_module_open" string="Open Modules" type="object" class="oe_highlight"/> or
<button special="cancel" string="Close" class="oe_link"/>
</div>
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<record id="action_view_base_module_import" model="ir.actions.act_window"> <record id="action_view_base_module_import" model="ir.actions.act_window">
<field name="name">Module Import</field> <field name="name">Import Module</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">base.module.import</field> <field name="res_model">base.import.module</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="target">new</field> <field name="target">new</field>
</record> </record>
<!-- This feature is now deprecated, but may come back later.
<menuitem <menuitem
name="Import Module" name="Import Module"
action="action_view_base_module_import" action="action_view_base_module_import"
id="menu_view_base_module_import" id="menu_view_base_module_import"
parent="menu_management" parent="base.menu_management"
groups="base.group_no_one" groups="base.group_no_one"
sequence="6"/> sequence="100"/>
-->
</data> </data>
</openerp> </openerp>

View File

@ -3,6 +3,7 @@ access_ crm_lead_report_assign,crm.lead.report.assign,model_crm_lead_report_assi
access_ crm_lead_report_assign_all,crm.lead.report.assign.all,model_crm_lead_report_assign,base.group_user,1,0,0,0 access_ crm_lead_report_assign_all,crm.lead.report.assign.all,model_crm_lead_report_assign,base.group_user,1,0,0,0
access_crm_partner_report,crm.partner.report.assign.all,model_crm_partner_report_assign,base.group_sale_salesman,1,0,0,0 access_crm_partner_report,crm.partner.report.assign.all,model_crm_partner_report_assign,base.group_sale_salesman,1,0,0,0
access_res_partner_grade,res.partner.grade,model_res_partner_grade,base.group_sale_salesman,1,1,1,0 access_res_partner_grade,res.partner.grade,model_res_partner_grade,base.group_sale_salesman,1,1,1,0
access_res_partner_grade_public,res.partner.grade,model_res_partner_grade,base.group_public,1,0,0,0
access_res_partner_grade_manager,res.partner.grade.manager,model_res_partner_grade,base.group_sale_manager,1,1,1,1 access_res_partner_grade_manager,res.partner.grade.manager,model_res_partner_grade,base.group_sale_manager,1,1,1,1
"access_partner_activation_manager","res.partner.activation.manager","model_res_partner_activation","base.group_partner_manager",1,1,1,1 "access_partner_activation_manager","res.partner.activation.manager","model_res_partner_activation","base.group_partner_manager",1,1,1,1
partner_access_crm_lead,crm.lead,model_crm_lead,base.group_portal,1,1,0,0 partner_access_crm_lead,crm.lead,model_crm_lead,base.group_portal,1,1,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_ crm_lead_report_assign_all crm.lead.report.assign.all model_crm_lead_report_assign base.group_user 1 0 0 0
4 access_crm_partner_report crm.partner.report.assign.all model_crm_partner_report_assign base.group_sale_salesman 1 0 0 0
5 access_res_partner_grade res.partner.grade model_res_partner_grade base.group_sale_salesman 1 1 1 0
6 access_res_partner_grade_public res.partner.grade model_res_partner_grade base.group_public 1 0 0 0
7 access_res_partner_grade_manager res.partner.grade.manager model_res_partner_grade base.group_sale_manager 1 1 1 1
8 access_partner_activation_manager res.partner.activation.manager model_res_partner_activation base.group_partner_manager 1 1 1 1
9 partner_access_crm_lead crm.lead model_crm_lead base.group_portal 1 1 0 0

View File

@ -192,15 +192,16 @@ class delivery_grid(osv.osv):
weight = 0 weight = 0
volume = 0 volume = 0
quantity = 0 quantity = 0
product_uom_obj = self.pool.get('product.uom')
for line in order.order_line: for line in order.order_line:
if not line.product_id or line.is_delivery: if not line.product_id or line.is_delivery:
continue continue
weight += (line.product_id.weight or 0.0) * line.product_uom_qty q = product_uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_uos_qty, line.product_id.uom_id.id)
volume += (line.product_id.volume or 0.0) * line.product_uom_qty weight += (line.product_id.weight or 0.0) * q
quantity += line.product_uom_qty volume += (line.product_id.volume or 0.0) * q
quantity += q
total = order.amount_total or 0.0 total = order.amount_total or 0.0
return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context) return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context)
def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None): def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None):

View File

@ -19,7 +19,7 @@
<tr> <tr>
<td><strong>Address</strong></td> <td><strong>Address</strong></td>
<td colspan="3"> <td colspan="3">
<div t-filed="o.employee_id.address_home_id" <div t-field="o.employee_id.address_home_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/> t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
</td> </td>
</tr> </tr>

View File

@ -114,6 +114,15 @@ class hr_applicant(osv.Model):
return int(department_ids[0][0]) return int(department_ids[0][0])
return None return None
def _get_default_company_id(self, cr, uid, department_id=None, context=None):
company_id = False
if department_id:
department = self.pool['hr.department'].browse(cr, uid, department_id, context=context)
company_id = department.company_id.id if department and department.company_id else False
if not company_id:
company_id = self.pool['res.company']._company_default_get(cr, uid, 'hr.applicant', context=context)
return company_id
def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
access_rights_uid = access_rights_uid or uid access_rights_uid = access_rights_uid or uid
stage_obj = self.pool.get('hr.recruitment.stage') stage_obj = self.pool.get('hr.recruitment.stage')
@ -231,7 +240,7 @@ class hr_applicant(osv.Model):
'user_id': lambda s, cr, uid, c: uid, 'user_id': lambda s, cr, uid, c: uid,
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c), 'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c), 'company_id': lambda s, cr, uid, c: s._get_default_company_id(cr, uid, s._get_default_department_id(cr, uid, c), c),
'color': 0, 'color': 0,
'date_last_stage_update': fields.datetime.now, 'date_last_stage_update': fields.datetime.now,
} }

View File

@ -8,7 +8,7 @@
<div class="oe_structure"/> <div class="oe_structure"/>
<div class="row"> <div class="row">
<div class="col-xs-4 col-xs-offset-7"> <div class="col-xs-5 col-xs-offset-7">
<div t-field="user.partner_id" <div t-field="user.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' /> t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
</div> </div>

View File

@ -669,9 +669,10 @@ class mail_thread(osv.AbstractModel):
def message_get_default_recipients(self, cr, uid, ids, context=None): def message_get_default_recipients(self, cr, uid, ids, context=None):
if context and context.get('thread_model') and context['thread_model'] in self.pool and context['thread_model'] != self._name: if context and context.get('thread_model') and context['thread_model'] in self.pool and context['thread_model'] != self._name:
sub_ctx = dict(context) if hasattr(self.pool[context['thread_model']], 'message_get_default_recipients'):
sub_ctx.pop('thread_model') sub_ctx = dict(context)
return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx) sub_ctx.pop('thread_model')
return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
res = {} res = {}
for record in self.browse(cr, SUPERUSER_ID, ids, context=context): for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
recipient_ids, email_to, email_cc = set(), False, False recipient_ids, email_to, email_cc = set(), False, False

View File

@ -919,15 +919,45 @@ openerp.mail = function (session) {
this.$('.oe_mail_expand').on('click', this.on_expand); this.$('.oe_mail_expand').on('click', this.on_expand);
this.$('.oe_mail_reduce').on('click', this.on_expand); this.$('.oe_mail_reduce').on('click', this.on_expand);
this.$('.oe_mail_action_model').on('click', this.on_record_clicked); this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
this.$('.oe_mail_action_author').on('click', this.on_record_author_clicked);
}, },
on_record_clicked: function (event) { on_record_clicked: function (event) {
event.preventDefault();
var self = this;
var state = { var state = {
'model': this.model, 'model': this.model,
'id': this.res_id, 'id': this.res_id,
'title': this.record_name 'title': this.record_name
}; };
session.webclient.action_manager.do_push_state(state); session.webclient.action_manager.do_push_state(state);
this.context.params = {
model: this.model,
res_id: this.res_id,
};
this.thread.ds_thread.call("message_redirect_action", {context: this.context}).then(function(action){
self.do_action(action);
});
},
on_record_author_clicked: function (event) {
event.preventDefault();
var partner_id = $(event.target).data('partner');
var state = {
'model': 'res.partner',
'id': partner_id,
'title': this.record_name
};
session.webclient.action_manager.do_push_state(state);
var action = {
type:'ir.actions.act_window',
view_type: 'form',
view_mode: 'form',
res_model: 'res.partner',
views: [[false, 'form']],
res_id: partner_id,
}
this.do_action(action);
}, },
/* Call the on_compose_message on the thread of this message. */ /* Call the on_compose_message on the thread of this message. */

View File

@ -276,7 +276,7 @@
<t t-if="widget.attachment_ids.length > 0"> <t t-if="widget.attachment_ids.length > 0">
<div class="oe_msg_attachment_list"></div> <div class="oe_msg_attachment_list"></div>
</t> </t>
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-esc="widget.author_id[2]"/></a> <a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-data-partner="widget.author_id[0]" class="oe_mail_action_author"><t t-esc="widget.author_id[2]"/></a>
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-esc="widget.author_id[2]"/></span> <span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-esc="widget.author_id[2]"/></span>
<t t-if="widget.type == 'notification'"> <t t-if="widget.type == 'notification'">
updated document updated document
@ -296,7 +296,7 @@
<t t-if="widget.type == 'notification' or ( (widget.type == 'email' or widget.type == 'comment') and (widget.subtype or widget.partner_ids.length > 0))" <t t-if="widget.type == 'notification' or ( (widget.type == 'email' or widget.type == 'comment') and (widget.subtype or widget.partner_ids.length > 0))"
t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner"> t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
<span t-attf-class="oe_partner_follower"> <span t-attf-class="oe_partner_follower">
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-esc="partner[1]"/></a> <a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}" t-att-data-partner="partner[0]" class="oe_mail_action_author"><t t-esc="partner[1]"/></a>
<t t-if="!widget.options.show_link" t-esc="partner[1]"/> <t t-if="!widget.options.show_link" t-esc="partner[1]"/>
</span> </span>
<t t-if="!partner_last">,</t> <t t-if="!partner_last">,</t>

View File

@ -4,10 +4,9 @@ from datetime import datetime
from dateutil import relativedelta from dateutil import relativedelta
import json import json
import random import random
import urllib
import urlparse
from openerp import tools from openerp import tools
from openerp.exceptions import Warning
from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp.osv import osv, fields from openerp.osv import osv, fields
@ -62,6 +61,12 @@ class MassMailingContact(osv.Model):
rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context) rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
return self.name_get(cr, uid, [rec_id], context)[0] return self.name_get(cr, uid, [rec_id], context)[0]
def message_get_default_recipients(self, cr, uid, ids, context=None):
res = {}
for record in self.browse(cr, uid, ids, context=context):
res[record.id] = {'partner_ids': [], 'email_to': record.email, 'email_cc': False}
return res
class MassMailingList(osv.Model): class MassMailingList(osv.Model):
"""Model of a contact list. """ """Model of a contact list. """
@ -549,6 +554,8 @@ class MassMailing(osv.Model):
for mailing in self.browse(cr, uid, ids, context=context): for mailing in self.browse(cr, uid, ids, context=context):
# instantiate an email composer + send emails # instantiate an email composer + send emails
res_ids = self.get_recipients(cr, uid, mailing, context=context) res_ids = self.get_recipients(cr, uid, mailing, context=context)
if not res_ids:
raise Warning('Please select recipients.')
comp_ctx = dict(context, active_ids=res_ids) comp_ctx = dict(context, active_ids=res_ids)
composer_values = { composer_values = {
'author_id': author_id, 'author_id': author_id,

View File

@ -22,7 +22,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p> <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div> </div>
</div> </div>
<div class="col-xs-4 col-xs-offset-2"> <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id" <div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' /> t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
</div> </div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -16,4 +16,7 @@ class AccountPaymentConfig(osv.TransientModel):
'module_payment_adyen': fields.boolean( 'module_payment_adyen': fields.boolean(
'Manage Payments Using Adyen', 'Manage Payments Using Adyen',
help='-It installs the module payment_adyen.'), help='-It installs the module payment_adyen.'),
'module_payment_buckaroo': fields.boolean(
'Manage Payments Using Buckaroo',
help='-It installs the module payment_buckaroo.'),
} }

View File

@ -20,6 +20,10 @@
<field name="module_payment_adyen" class="oe_inline"/> <field name="module_payment_adyen" class="oe_inline"/>
<label for="module_payment_adyen"/> <label for="module_payment_adyen"/>
</div> </div>
<div>
<field name="module_payment_buckaroo" class="oe_inline"/>
<label for="module_payment_buckaroo"/>
</div>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@ -20,7 +20,7 @@
<input t-if="tx_values.get('merchantReturnData')" type='hidden' name='merchantReturnData' <input t-if="tx_values.get('merchantReturnData')" type='hidden' name='merchantReturnData'
t-att-value="tx_values.get('merchantReturnData')"/> t-att-value="tx_values.get('merchantReturnData')"/>
<!-- submit --> <!-- submit -->
<button type="image" name="submit" width="100px" <button type="submit" width="100px"
t-att-class="submit_class"> t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_adyen/static/src/img/adyen_icon.png"/> <img t-if="not submit_txt" src="/payment_adyen/static/src/img/adyen_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span> <span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2014-Today OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import models
import controllers

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
{
'name': 'Buckaroo Payment Acquirer',
'category': 'Hidden',
'summary': 'Payment Acquirer: Buckaroo Implementation',
'version': '1.0',
'description': """Buckaroo Payment Acquirer""",
'author': 'OpenERP SA',
'depends': ['payment'],
'data': [
'views/buckaroo.xml',
'views/payment_acquirer.xml',
'data/buckaroo.xml',
],
'installable': True,
}

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import main

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
try:
import simplejson as json
except ImportError:
import json
import logging
import pprint
import werkzeug
from openerp import http, SUPERUSER_ID
from openerp.http import request
_logger = logging.getLogger(__name__)
class BuckarooController(http.Controller):
_return_url = '/payment/buckaroo/return'
_cancel_url = '/payment/buckaroo/cancel'
_exception_url = '/payment/buckaroo/error'
_reject_url = '/payment/buckaroo/reject'
@http.route([
'/payment/buckaroo/return',
'/payment/buckaroo/cancel',
'/payment/buckaroo/error',
'/payment/buckaroo/reject',
], type='http', auth='none')
def buckaroo_return(self, **post):
""" Buckaroo."""
_logger.info('Buckaroo: entering form_feedback with post data %s', pprint.pformat(post)) # debug
request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'buckaroo', context=request.context)
return_url = post.pop('return_url', '')
if not return_url:
data ='' + post.pop('ADD_RETURNDATA', '{}').replace("'", "\"")
custom = json.loads(data)
return_url = custom.pop('return_url', '/')
return werkzeug.utils.redirect(return_url)

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="payment_acquirer_buckaroo" model="payment.acquirer">
<field name="name">Buckaroo</field>
<field name="provider">buckaroo</field>
<field name="company_id" ref="base.main_company"/>
<field name="view_template_id" ref="buckaroo_acquirer_button"/>
<field name="environment">test</field>
<field name="pre_msg"><![CDATA[
<p>You will be redirected to the Buckaroo website after cliking on the payment button.</p>]]></field>
<field name="brq_websitekey">dummy</field>
<field name="brq_secretkey">dummy</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import buckaroo

View File

@ -0,0 +1,191 @@
# -*- coding: utf-'8' "-*-"
from hashlib import sha1
import logging
import urlparse
from openerp.addons.payment.models.payment_acquirer import ValidationError
from openerp.addons.payment_buckaroo.controllers.main import BuckarooController
from openerp.osv import osv, fields
from openerp.tools.float_utils import float_compare
_logger = logging.getLogger(__name__)
class AcquirerBuckaroo(osv.Model):
_inherit = 'payment.acquirer'
def _get_buckaroo_urls(self, cr, uid, environment, context=None):
""" Buckaroo URLs
"""
if environment == 'prod':
return {
'buckaroo_form_url': 'https://checkout.buckaroo.nl/html/',
}
else:
return {
'buckaroo_form_url': 'https://testcheckout.buckaroo.nl/html/',
}
def _get_providers(self, cr, uid, context=None):
providers = super(AcquirerBuckaroo, self)._get_providers(cr, uid, context=context)
providers.append(['buckaroo', 'Buckaroo'])
return providers
_columns = {
'brq_websitekey': fields.char('WebsiteKey', required_if_provider='buckaroo'),
'brq_secretkey': fields.char('SecretKey', required_if_provider='buckaroo'),
}
def _buckaroo_generate_digital_sign(self, acquirer, inout, values):
""" Generate the shasign for incoming or outgoing communications.
:param browse acquirer: the payment.acquirer browse record. It should
have a shakey in shaky out
:param string inout: 'in' (openerp contacting buckaroo) or 'out' (buckaroo
contacting openerp).
:param dict values: transaction values
:return string: shasign
"""
assert inout in ('in', 'out')
assert acquirer.provider == 'buckaroo'
keys = "add_returndata Brq_amount Brq_culture Brq_currency Brq_invoicenumber Brq_return Brq_returncancel Brq_returnerror Brq_returnreject brq_test Brq_websitekey".split()
def get_value(key):
if values.get(key):
return values[key]
return ''
if inout == 'out':
if 'BRQ_SIGNATURE' in values:
del values['BRQ_SIGNATURE']
items = sorted((k.upper(), v) for k, v in values.items())
sign = ''.join('%s=%s' % (k, v) for k, v in items)
else:
sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
#Add the pre-shared secret key at the end of the signature
sign = sign + acquirer.brq_secretkey
if isinstance(sign, str):
sign = urlparse.parse_qsl(sign)
shasign = sha1(sign).hexdigest()
return shasign
def buckaroo_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
acquirer = self.browse(cr, uid, id, context=context)
buckaroo_tx_values = dict(tx_values)
buckaroo_tx_values.update({
'Brq_websitekey': acquirer.brq_websitekey,
'Brq_amount': tx_values['amount'],
'Brq_currency': tx_values['currency'] and tx_values['currency'].name or '',
'Brq_invoicenumber': tx_values['reference'],
'brq_test' : True,
'Brq_return': '%s' % urlparse.urljoin(base_url, BuckarooController._return_url),
'Brq_returncancel': '%s' % urlparse.urljoin(base_url, BuckarooController._cancel_url),
'Brq_returnerror': '%s' % urlparse.urljoin(base_url, BuckarooController._exception_url),
'Brq_returnreject': '%s' % urlparse.urljoin(base_url, BuckarooController._reject_url),
'Brq_culture': 'en-US',
})
if buckaroo_tx_values.get('return_url'):
buckaroo_tx_values['add_returndata'] = {'return_url': '%s' % buckaroo_tx_values.pop('return_url')}
else:
buckaroo_tx_values['add_returndata'] = ''
buckaroo_tx_values['Brq_signature'] = self._buckaroo_generate_digital_sign(acquirer, 'in', buckaroo_tx_values)
return partner_values, buckaroo_tx_values
def buckaroo_get_form_action_url(self, cr, uid, id, context=None):
acquirer = self.browse(cr, uid, id, context=context)
return self._get_buckaroo_urls(cr, uid, acquirer.environment, context=context)['buckaroo_form_url']
class TxBuckaroo(osv.Model):
_inherit = 'payment.transaction'
# buckaroo status
_buckaroo_valid_tx_status = [190]
_buckaroo_pending_tx_status = [790, 791, 792, 793]
_buckaroo_cancel_tx_status = [890, 891]
_buckaroo_error_tx_status = [490, 491, 492]
_buckaroo_reject_tx_status = [690]
_columns = {
'buckaroo_txnid': fields.char('Transaction ID'),
}
# --------------------------------------------------
# FORM RELATED METHODS
# --------------------------------------------------
def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
""" Given a data dict coming from buckaroo, verify it and find the related
transaction record. """
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
if not reference or not pay_id or not shasign:
error_msg = 'Buckaroo: received data with missing reference (%s) or pay_id (%s) or shashign (%s)' % (reference, pay_id, shasign)
_logger.error(error_msg)
raise ValidationError(error_msg)
tx_ids = self.search(cr, uid, [('reference', '=', reference)], context=context)
if not tx_ids or len(tx_ids) > 1:
error_msg = 'Buckaroo: received data for reference %s' % (reference)
if not tx_ids:
error_msg += '; no order found'
else:
error_msg += '; multiple order found'
_logger.error(error_msg)
raise ValidationError(error_msg)
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
#verify shasign
shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out' ,data)
if shasign_check.upper() != shasign.upper():
error_msg = 'Buckaroo: invalid shasign, received %s, computed %s, for data %s' % (shasign, shasign_check, data)
_logger.error(error_msg)
raise ValidationError(error_msg)
return tx
def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
invalid_parameters = []
if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
# check what is buyed
if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), tx.amount, 2) != 0:
invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % tx.amount))
if data.get('BRQ_CURRENCY') != tx.currency_id.name:
invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), tx.currency_id.name))
return invalid_parameters
def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
status_code = int(data.get('BRQ_STATUSCODE','0'))
if status_code in self._buckaroo_valid_tx_status:
tx.write({
'state': 'done',
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
})
return True
elif status_code in self._buckaroo_pending_tx_status:
tx.write({
'state': 'pending',
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
})
return True
elif status_code in self._buckaroo_cancel_tx_status:
tx.write({
'state': 'cancel',
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
})
return True
else:
error = 'Buckaroo: feedback error'
_logger.info(error)
tx.write({
'state': 'error',
'state_message': error,
'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
})
return False

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.payment_buckaroo.tests import test_buckaroo
checks = [
test_buckaroo,
]

View File

@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
from lxml import objectify
import urlparse
import openerp
from openerp.addons.payment.models.payment_acquirer import ValidationError
from openerp.addons.payment.tests.common import PaymentAcquirerCommon
from openerp.addons.payment_buckaroo.controllers.main import BuckarooController
from openerp.tools import mute_logger
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(False)
class BuckarooCommon(PaymentAcquirerCommon):
def setUp(self):
super(BuckarooCommon, self).setUp()
cr, uid = self.cr, self.uid
self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
# get the buckaroo account
model, self.buckaroo_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_buckaroo', 'payment_acquirer_buckaroo')
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(False)
class BuckarooForm(BuckarooCommon):
def test_10_Buckaroo_form_render(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid things
buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None)
self.assertEqual(buckaroo.environment, 'test', 'test without test environment')
# ----------------------------------------
# Test: button direct rendering
# ----------------------------------------
form_values = {
'add_returndata': None,
'Brq_websitekey': buckaroo.brq_websitekey,
'Brq_amount': '2240.0',
'Brq_currency': 'EUR',
'Brq_invoicenumber': 'SO004',
'Brq_signature': '1b8c10074c622d965272a91a9e88b5b3777d2474', # update me
'brq_test': 'True',
'Brq_return': '%s' % urlparse.urljoin(self.base_url, BuckarooController._return_url),
'Brq_returncancel': '%s' % urlparse.urljoin(self.base_url, BuckarooController._cancel_url),
'Brq_returnerror': '%s' % urlparse.urljoin(self.base_url, BuckarooController._exception_url),
'Brq_returnreject': '%s' % urlparse.urljoin(self.base_url, BuckarooController._reject_url),
'Brq_culture': 'en-US',
}
# render the button
res = self.payment_acquirer.render(
cr, uid, self.buckaroo_id,
'SO004', 2240.0, self.currency_euro_id,
partner_id=None,
partner_values=self.buyer_values,
context=context)
# check form result
tree = objectify.fromstring(res)
self.assertEqual(tree.get('action'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
for form_input in tree.input:
if form_input.get('name') in ['submit']:
continue
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'Buckaroo: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
)
# ----------------------------------------
# Test2: button using tx + validation
# ----------------------------------------
# create a new draft tx
tx_id = self.payment_transaction.create(
cr, uid, {
'amount': 2240.0,
'acquirer_id': self.buckaroo_id,
'currency_id': self.currency_euro_id,
'reference': 'SO004',
'partner_id': self.buyer_id,
}, context=context
)
# render the button
res = self.payment_acquirer.render(
cr, uid, self.buckaroo_id,
'should_be_erased', 2240.0, self.currency_euro,
tx_id=tx_id,
partner_id=None,
partner_values=self.buyer_values,
context=context)
# check form result
tree = objectify.fromstring(res)
self.assertEqual(tree.get('action'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
for form_input in tree.input:
if form_input.get('name') in ['submit']:
continue
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'Buckaroo: wrong value for form input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
)
@mute_logger('openerp.addons.payment_buckaroo.models.buckaroo', 'ValidationError')
def test_20_buckaroo_form_management(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid thing
buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None)
self.assertEqual(buckaroo.environment, 'test', 'test without test environment')
# typical data posted by buckaroo after client has successfully paid
buckaroo_post_data = {
'BRQ_RETURNDATA': u'',
'BRQ_AMOUNT': u'2240.00',
'BRQ_CURRENCY': u'EUR',
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
'BRQ_INVOICENUMBER': u'SO004',
'BRQ_PAYMENT': u'573311D081B04069BD6336001611DBD4',
'BRQ_PAYMENT_METHOD': u'paypal',
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
'BRQ_SERVICE_PAYPAL_PAYERFIRSTNAME': u'Jan',
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
'BRQ_SIGNATURE': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
'BRQ_STATUSCODE': u'190',
'BRQ_STATUSCODE_DETAIL': u'S001',
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
'BRQ_TEST': u'true',
'BRQ_TIMESTAMP': u'2014-05-08 12:41:21',
'BRQ_TRANSACTIONS': u'D6106678E1D54EEB8093F5B3AC42EA7B',
'BRQ_WEBSITEKEY': u'5xTGyGyPyl',
}
# should raise error about unknown tx
with self.assertRaises(ValidationError):
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
tx_id = self.payment_transaction.create(
cr, uid, {
'amount': 2240.0,
'acquirer_id': self.buckaroo_id,
'currency_id': self.currency_euro_id,
'reference': 'SO004',
'partner_name': 'Norbert Buyer',
'partner_country_id': self.country_france_id,
}, context=context
)
# validate it
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
# check state
tx = self.payment_transaction.browse(cr, uid, tx_id, context=context)
self.assertEqual(tx.state, 'done', 'Buckaroo: validation did not put tx into done state')
self.assertEqual(tx.buckaroo_txnid, buckaroo_post_data.get('BRQ_TRANSACTIONS'), 'Buckaroo: validation did not update tx payid')
# reset tx
tx.write({'state': 'draft', 'date_validate': False, 'buckaroo_txnid': False})
# now buckaroo post is ok: try to modify the SHASIGN
buckaroo_post_data['BRQ_SIGNATURE'] = '54d928810e343acf5fb0c3ee75fd747ff159ef7a'
with self.assertRaises(ValidationError):
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
# simulate an error
buckaroo_post_data['BRQ_STATUSCODE'] = 2
buckaroo_post_data['BRQ_SIGNATURE'] = '4164b52adb1e6a2221d3d8a39d8c3e18a9ecb90b'
self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
# check state
tx = self.payment_transaction.browse(cr, uid, tx_id, context=context)
self.assertEqual(tx.state, 'error', 'Buckaroo: erroneous validation did not put tx into error state')

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<template id="buckaroo_acquirer_button">
<form t-if="acquirer.brq_websitekey" t-att-action="tx_url" method="post" target="_self">
<input type="hidden" name="Brq_websitekey" t-att-value="tx_values['Brq_websitekey']"/>
<input type="hidden" name="Brq_amount" t-att-value="tx_values['Brq_amount'] or '0.0'"/>
<input type="hidden" name="Brq_currency" t-att-value="tx_values['Brq_currency']"/>
<input type="hidden" name="Brq_invoicenumber" t-att-value="tx_values['Brq_invoicenumber']"/>
<input type="hidden" name="Brq_signature" t-att-value="tx_values['Brq_signature']"/>
<input type="hidden" name="brq_test" t-att-value="tx_values['brq_test']"/>
<input type="hidden" name="Brq_culture" t-att-value="tx_values['Brq_culture']"/>
<!-- URLs -->
<input t-if="tx_values.get('Brq_return')" type='hidden' name='Brq_return'
t-att-value="tx_values.get('Brq_return')"/>
<input t-if="tx_values.get('Brq_returncancel')" type='hidden' name='Brq_returncancel'
t-att-value="tx_values.get('Brq_returncancel')"/>
<input t-if="tx_values.get('Brq_returnerror')" type='hidden' name='Brq_returnerror'
t-att-value="tx_values.get('Brq_returnerror')"/>
<input t-if="tx_values.get('Brq_returnreject')" type='hidden' name='Brq_returnreject'
t-att-value="tx_values.get('Brq_returnreject')"/>
<input type='hidden' name='add_returndata' t-att-value="tx_values.get('add_returndata')"/>
<!-- submit -->
<button type="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_buckaroo/static/src/img/buckaroo_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
</button>
</form>
</template>
</data>
</openerp>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="acquirer_form_buckaroo" model="ir.ui.view">
<field name="name">acquirer.form.buckaroo</field>
<field name="model">payment.acquirer</field>
<field name="inherit_id" ref="payment.acquirer_form"/>
<field name="arch" type="xml">
<xpath expr='//group[@name="acquirer_display"]' position='after'>
<group attrs="{'invisible': [('provider', '!=', 'buckaroo')]}">
<field name="brq_websitekey"/>
<field name="brq_secretkey"/>
</group>
</xpath>
</field>
</record>
<record id="transaction_form_buckaroo" model="ir.ui.view">
<field name="name">acquirer.transaction.form.buckaroo</field>
<field name="model">payment.transaction</field>
<field name="inherit_id" ref="payment.transaction_form"/>
<field name="arch" type="xml">
<xpath expr='//notebook' position='inside'>
<page string="Buckaroo TX Details">
<group>
<field name="buckaroo_txnid"/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -41,7 +41,7 @@
<input type='hidden' name='EXCEPTIONURL' t-att-value='tx_values["EXCEPTIONURL"]'/> <input type='hidden' name='EXCEPTIONURL' t-att-value='tx_values["EXCEPTIONURL"]'/>
<input type='hidden' name='CANCELURL' t-att-value='tx_values["CANCELURL"]'/> <input type='hidden' name='CANCELURL' t-att-value='tx_values["CANCELURL"]'/>
<!-- submit --> <!-- submit -->
<button type="image" name="submit" width="100px" <button type="submit" width="100px"
t-att-class="submit_class"> t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_ogone/static/src/img/ogone_icon.png"/> <img t-if="not submit_txt" src="/payment_ogone/static/src/img/ogone_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span> <span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>

View File

@ -31,7 +31,7 @@
<input t-if="tx_values.get('cancel_return')" type="hidden" name="cancel_return" <input t-if="tx_values.get('cancel_return')" type="hidden" name="cancel_return"
t-att-value="tx_values.get('cancel_return')"/> t-att-value="tx_values.get('cancel_return')"/>
<!-- submit --> <!-- submit -->
<button type="image" name="submit" width="100px" <button type="submit" width="100px"
t-att-class="submit_class"> t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_paypal/static/src/img/paypal_icon.png"/> <img t-if="not submit_txt" src="/payment_paypal/static/src/img/paypal_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span> <span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>

View File

@ -11,7 +11,7 @@
<input type='hidden' name='amount' t-att-value='amount or "0.0"'/> <input type='hidden' name='amount' t-att-value='amount or "0.0"'/>
<input type='hidden' name='currency' t-att-value='currency.name'/> <input type='hidden' name='currency' t-att-value='currency.name'/>
<!-- submit --> <!-- submit -->
<button name="submit" width="100px" <button type="submit" width="100px"
t-att-class="submit_class"> t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_transfer/static/src/img/transfer_icon.png"/> <img t-if="not submit_txt" src="/payment_transfer/static/src/img/transfer_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span> <span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>

View File

@ -111,7 +111,7 @@
<p style="border-left: 1px solid #8e0000; margin-left: 30px;"> <p style="border-left: 1px solid #8e0000; margin-left: 30px;">
&nbsp;&nbsp;<strong>REFERENCES</strong><br /> &nbsp;&nbsp;<strong>REFERENCES</strong><br />
&nbsp;&nbsp;Order number: <strong>${object.name}</strong><br /> &nbsp;&nbsp;Order number: <strong>${object.name}</strong><br />
&nbsp;&nbsp;Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br /> &nbsp;&nbsp;Order total: <strong>${object.amount_total} ${object.currency_id.name}</strong><br />
&nbsp;&nbsp;Order date: ${object.date_order}<br /> &nbsp;&nbsp;Order date: ${object.date_order}<br />
% if object.origin: % if object.origin:
&nbsp;&nbsp;Order reference: ${object.origin}<br /> &nbsp;&nbsp;Order reference: ${object.origin}<br />

View File

@ -220,7 +220,7 @@ class purchase_order(osv.osv):
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."), 'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"), 'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'), 'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"), 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been validated"),
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'), 'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True, 'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True,
readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]}, readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},

View File

@ -23,7 +23,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p> <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div> </div>
</div> </div>
<div class="col-xs-4 col-xs-offset-2"> <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id" <div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/> t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
</div> </div>

View File

@ -23,7 +23,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p> <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div> </div>
</div> </div>
<div class="col-xs-4 col-xs-offset-2"> <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id" <div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/> t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
</div> </div>

View File

@ -66,7 +66,7 @@ class ReportController(Controller):
# Misc. route utils # Misc. route utils
#------------------------------------------------------ #------------------------------------------------------
@route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user") @route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user")
def report_barcode(self, type, value, width=300, height=50): def report_barcode(self, type, value, width=600, height=100):
"""Contoller able to render barcode images thanks to reportlab. """Contoller able to render barcode images thanks to reportlab.
Samples: Samples:
<img t-att-src="'/report/barcode/QR/%s' % o.name"/> <img t-att-src="'/report/barcode/QR/%s' % o.name"/>

View File

@ -9,7 +9,7 @@
<field name="page_width">0</field> <field name="page_width">0</field>
<field name="orientation">Portrait</field> <field name="orientation">Portrait</field>
<field name="margin_top">40</field> <field name="margin_top">40</field>
<field name="margin_bottom">20</field> <field name="margin_bottom">23</field>
<field name="margin_left">7</field> <field name="margin_left">7</field>
<field name="margin_right">7</field> <field name="margin_right">7</field>
<field name="header_line" eval="False" /> <field name="header_line" eval="False" />

View File

@ -25,10 +25,8 @@ from openerp.tools.translate import _
from openerp.addons.web.http import request from openerp.addons.web.http import request
from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.safe_eval import safe_eval as eval
import os import re
import time import time
import psutil
import signal
import base64 import base64
import logging import logging
import tempfile import tempfile
@ -52,15 +50,19 @@ try:
['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE ['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
) )
except OSError: except OSError:
_logger.error('You need wkhtmltopdf to print a pdf version of the reports.') _logger.info('You need wkhtmltopdf to print a pdf version of the reports.')
else: else:
out, err = process.communicate() out, err = process.communicate()
version = out.splitlines()[1].strip() version = re.search('([0-9.]+)', out).group(0)
version = version.split(' ')[1]
if LooseVersion(version) < LooseVersion('0.12.0'): if LooseVersion(version) < LooseVersion('0.12.0'):
_logger.warning('Upgrade wkhtmltopdf to (at least) 0.12.0') _logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0')
wkhtmltopdf_state = 'upgrade' wkhtmltopdf_state = 'upgrade'
wkhtmltopdf_state = 'ok' else:
wkhtmltopdf_state = 'ok'
if config['workers'] == 1:
_logger.info('You need to start OpenERP with at least two workers to print a pdf version of the reports.')
wkhtmltopdf_state = 'workers'
class Report(osv.Model): class Report(osv.Model):
@ -343,19 +345,20 @@ class Report(osv.Model):
command_args = [] command_args = []
tmp_dir = tempfile.gettempdir() tmp_dir = tempfile.gettempdir()
# Passing the cookie to wkhtmltopdf in order to resolve URL. # Passing the cookie to wkhtmltopdf in order to resolve internal links.
try: try:
if request: if request:
command_args.extend(['--cookie', 'session_id', request.session.sid]) command_args.extend(['--cookie', 'session_id', request.session.sid])
except AttributeError: except AttributeError:
pass pass
# Display arguments # Wkhtmltopdf arguments
command_args.extend(['--quiet']) # Less verbose error messages
if paperformat: if paperformat:
# Convert the paperformat record into arguments
command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args)) command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args))
command_args.extend(['--load-error-handling', 'ignore']) # Force the landscape orientation if necessary
if landscape and '--orientation' in command_args: if landscape and '--orientation' in command_args:
command_args_copy = list(command_args) command_args_copy = list(command_args)
for index, elem in enumerate(command_args_copy): for index, elem in enumerate(command_args_copy):
@ -366,61 +369,46 @@ class Report(osv.Model):
elif landscape and not '--orientation' in command_args: elif landscape and not '--orientation' in command_args:
command_args.extend(['--orientation', 'landscape']) command_args.extend(['--orientation', 'landscape'])
# Execute WKhtmltopdf
pdfdocuments = [] pdfdocuments = []
# HTML to PDF thanks to WKhtmltopdf
for index, reporthtml in enumerate(bodies): for index, reporthtml in enumerate(bodies):
command_arg_local = [] local_command_args = []
pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', mode='w+b')
mode='w+b')
# Directly load the document if we have it # Directly load the document if we already have it
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]): if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0])) pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
pdfreport.seek(0) pdfreport.seek(0)
pdfdocuments.append(pdfreport) pdfdocuments.append(pdfreport)
continue continue
# Header stuff # Wkhtmltopdf handles header/footer as separate pages. Create them if necessary.
if headers: if headers:
head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+')
dir=tmp_dir, mode='w+')
head_file.write(headers[index]) head_file.write(headers[index])
head_file.seek(0) head_file.seek(0)
command_arg_local.extend(['--header-html', head_file.name]) local_command_args.extend(['--header-html', head_file.name])
# Footer stuff
if footers: if footers:
foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+')
dir=tmp_dir, mode='w+')
foot_file.write(footers[index]) foot_file.write(footers[index])
foot_file.seek(0) foot_file.seek(0)
command_arg_local.extend(['--footer-html', foot_file.name]) local_command_args.extend(['--footer-html', foot_file.name])
# Body stuff # Body stuff
content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+')
dir=tmp_dir, mode='w+')
content_file.write(reporthtml[1]) content_file.write(reporthtml[1])
content_file.seek(0) content_file.seek(0)
try: try:
# If the server is running with only one worker, ask to create a secund to be able wkhtmltopdf = command + command_args + local_command_args
# to serve the http request of wkhtmltopdf subprocess.
if config['workers'] == 1:
ppid = psutil.Process(os.getpid()).ppid
os.kill(ppid, signal.SIGTTIN)
wkhtmltopdf = command + command_args + command_arg_local
wkhtmltopdf += [content_file.name] + [pdfreport.name] wkhtmltopdf += [content_file.name] + [pdfreport.name]
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stderr=subprocess.PIPE)
out, err = process.communicate() out, err = process.communicate()
if config['workers'] == 1: if process.returncode not in [0, 1]:
os.kill(ppid, signal.SIGTTOU)
if process.returncode != 0:
raise osv.except_osv(_('Report (PDF)'), raise osv.except_osv(_('Report (PDF)'),
_('wkhtmltopdf failed with error code = %s. ' _('Wkhtmltopdf failed (error code: %s). '
'Message: %s') % (str(process.returncode), err)) 'Message: %s') % (str(process.returncode), err))
# Save the pdf in attachment if marked # Save the pdf in attachment if marked
@ -446,7 +434,7 @@ class Report(osv.Model):
except: except:
raise raise
# Get and return the full pdf # Return the entire document
if len(pdfdocuments) == 1: if len(pdfdocuments) == 1:
content = pdfdocuments[0].read() content = pdfdocuments[0].read()
pdfdocuments[0].close() pdfdocuments[0].close()

View File

@ -54,33 +54,35 @@ openerp.report = function(instance) {
if (action.report_type == 'qweb-html') { if (action.report_type == 'qweb-html') {
window.open(report_url, '_blank', 'height=900,width=1280'); window.open(report_url, '_blank', 'height=900,width=1280');
instance.web.unblockUI(); instance.web.unblockUI();
} else { } else if (action.report_type === 'qweb-pdf') {
// Trigger the download of the pdf/controller report // Trigger the download of the pdf/controller report
(wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
if (action.report_type == 'qweb-pdf') { // Fallback on html if wkhtmltopdf is not installed or if OpenERP is started with one worker
(wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) { if (presence === 'install') {
// Fallback of qweb-pdf if wkhtmltopdf is not installed self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
if (presence == 'install' && action.report_type == 'qweb-pdf') { system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\
self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \ wkhtmltopdf.org</a>'), true);
system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\ report_url = report_url.substring(12)
wkhtmltopdf.org</a>'), true); window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
report_url = report_url.substring(12) instance.web.unblockUI();
window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024'); return;
instance.web.unblockUI(); } else if (presence === 'workers') {
return; self.do_notify(_t('Report'), _t('You need to start OpenERP with at least two \
} else { workers to print a pdf version of the reports.'), true);
if (presence == 'upgrade') { report_url = report_url.substring(12)
self.do_notify(_t('Report'), _t('You should upgrade your version of\ window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\ instance.web.unblockUI();
support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \ return;
target="_blank">wkhtmltopdf.org</a>'), true); } else if (presence === 'upgrade') {
} self.do_notify(_t('Report'), _t('You should upgrade your version of\
} Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
return trigger_download(self.session, response, c); support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
}); target="_blank">wkhtmltopdf.org</a>'), true);
} else if (action.report_type == 'controller') { }
return trigger_download(self.session, response, c); return trigger_download(self.session, response, c);
} });
} else if (action.report_type === 'controller') {
return trigger_download(self.session, response, c);
} }
} else { } else {
return self._super(action, options); return self._super(action, options);

View File

@ -9,7 +9,8 @@
t-att-data-main-object="repr(main_object) if editable else None" t-att-data-main-object="repr(main_object) if editable else None"
t-att-data-report-margin-top="data_report_margin_top if data_report_margin_top else None" t-att-data-report-margin-top="data_report_margin_top if data_report_margin_top else None"
t-att-data-report-header-spacing="data_report_header_spacing if data_report_header_spacing else None" t-att-data-report-header-spacing="data_report_header_spacing if data_report_header_spacing else None"
t-att-data-report-dpi="data_report_dpi if data_report_dpi else None"> t-att-data-report-dpi="data_report_dpi if data_report_dpi else None"
t-att-data-oe-company-name="res_company.name">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1"/>
@ -19,7 +20,6 @@
<t t-if="not title and main_object and 'name' in main_object"> <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-set="additional_title" t-value="main_object.name"/>
</t> </t>
<meta name="openerp.company" t-att-value="res_company.name"/>
<meta name="description" t-att-value="main_object and 'website_meta_description' in main_object <meta name="description" t-att-value="main_object and 'website_meta_description' in main_object
and main_object.website_meta_description or website_meta_description"/> and main_object.website_meta_description or website_meta_description"/>
<meta name="keywords" t-att-value="main_object and 'website_meta_keywords' in main_object <meta name="keywords" t-att-value="main_object and 'website_meta_keywords' in main_object

View File

@ -5,7 +5,7 @@
<t t-call="report.external_layout"> <t t-call="report.external_layout">
<div class="page"> <div class="page">
<div class="row"> <div class="row">
<div class="col-xs-4 col-xs-offset-8"> <div class="col-xs-5 col-xs-offset-7">
<address t-field="o.partner_id" <address t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' /> t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
<span t-field="o.partner_id.vat"/> <span t-field="o.partner_id.vat"/>

View File

@ -156,8 +156,8 @@ class WebKitParser(report_sxw):
"""Call webkit in order to generate pdf""" """Call webkit in order to generate pdf"""
if not webkit_header: if not webkit_header:
webkit_header = report_xml.webkit_header webkit_header = report_xml.webkit_header
tmp_dir = tempfile.gettempdir() fd, out_filename = tempfile.mkstemp(suffix=".pdf",
out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.") prefix="webkit.tmp.")
file_to_del = [out_filename] file_to_del = [out_filename]
if comm_path: if comm_path:
command = [comm_path] command = [comm_path]
@ -168,25 +168,15 @@ class WebKitParser(report_sxw):
# default to UTF-8 encoding. Use <meta charset="latin-1"> to override. # default to UTF-8 encoding. Use <meta charset="latin-1"> to override.
command.extend(['--encoding', 'utf-8']) command.extend(['--encoding', 'utf-8'])
if header : if header :
head_file = file( os.path.join( with tempfile.NamedTemporaryFile(suffix=".head.html",
tmp_dir, delete=False) as head_file:
str(time.time()) + '.head.html' head_file.write(self._sanitize_html(header.encode('utf-8')))
),
'w'
)
head_file.write(self._sanitize_html(header.encode('utf-8')))
head_file.close()
file_to_del.append(head_file.name) file_to_del.append(head_file.name)
command.extend(['--header-html', head_file.name]) command.extend(['--header-html', head_file.name])
if footer : if footer :
foot_file = file( os.path.join( with tempfile.NamedTemporaryFile(suffix=".foot.html",
tmp_dir, delete=False) as foot_file:
str(time.time()) + '.foot.html' foot_file.write(self._sanitize_html(footer.encode('utf-8')))
),
'w'
)
foot_file.write(self._sanitize_html(footer.encode('utf-8')))
foot_file.close()
file_to_del.append(foot_file.name) file_to_del.append(foot_file.name)
command.extend(['--footer-html', foot_file.name]) command.extend(['--footer-html', foot_file.name])
@ -204,10 +194,10 @@ class WebKitParser(report_sxw):
command.extend(['--page-size', str(webkit_header.format).replace(',', '.')]) command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
count = 0 count = 0
for html in html_list : for html in html_list :
html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w') with tempfile.NamedTemporaryFile(suffix="%d.body.html" %count,
count += 1 delete=False) as html_file:
html_file.write(self._sanitize_html(html.encode('utf-8'))) count += 1
html_file.close() html_file.write(self._sanitize_html(html.encode('utf-8')))
file_to_del.append(html_file.name) file_to_del.append(html_file.name)
command.append(html_file.name) command.append(html_file.name)
command.append(out_filename) command.append(out_filename)
@ -227,9 +217,9 @@ class WebKitParser(report_sxw):
if status : if status :
raise except_osv(_('Webkit error' ), raise except_osv(_('Webkit error' ),
_("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message)) _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
pdf_file = open(out_filename, 'rb') with open(out_filename, 'rb') as pdf_file:
pdf = pdf_file.read() pdf = pdf_file.read()
pdf_file.close() os.close(fd)
finally: finally:
if stderr_fd is not None: if stderr_fd is not None:
os.close(stderr_fd) os.close(stderr_fd)

View File

@ -19,7 +19,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p> <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div> </div>
</div> </div>
<div class="col-xs-4 col-xs-offset-2"> <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id" <div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' /> t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
</div> </div>

View File

@ -1,11 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_survey_public,survey.survey public,model_survey_survey,base.group_public,1,0,0,0 access_survey_public,survey.survey public,model_survey_survey,,1,0,0,0
access_survey_page_public,survey.page public,model_survey_page,base.group_public,1,0,0,0 access_survey_page_public,survey.page public,model_survey_page,,1,0,0,0
access_survey_question_public,survey.question public,model_survey_question,base.group_public,1,0,0,0 access_survey_question_public,survey.question public,model_survey_question,,1,0,0,0
access_survey_label_public,survey.label public,model_survey_label,base.group_public,1,0,0,0 access_survey_label_public,survey.label public,model_survey_label,,1,0,0,0
access_survey_user_input_public,survey.user_input public,model_survey_user_input,base.group_public,1,1,1,0 access_survey_user_input_public,survey.user_input public,model_survey_user_input,,1,1,1,0
access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,base.group_public,1,1,1,0 access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,,1,1,1,0
access_survey_stage_public,survey.stage public,model_survey_stage,base.group_public,1,0,0,0 access_survey_stage_public,survey.stage public,model_survey_stage,,1,0,0,0
access_survey_user,survey.survey user,model_survey_survey,base.group_survey_user,1,0,0,0 access_survey_user,survey.survey user,model_survey_survey,base.group_survey_user,1,0,0,0
access_survey_page_user,survey.page user,model_survey_page,base.group_survey_user,1,0,0,0 access_survey_page_user,survey.page user,model_survey_page,base.group_survey_user,1,0,0,0
access_survey_question_user,survey.question user,model_survey_question,base.group_survey_user,1,0,0,0 access_survey_question_user,survey.question user,model_survey_question,base.group_survey_user,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_survey_public survey.survey public model_survey_survey base.group_public 1 0 0 0
3 access_survey_page_public survey.page public model_survey_page base.group_public 1 0 0 0
4 access_survey_question_public survey.question public model_survey_question base.group_public 1 0 0 0
5 access_survey_label_public survey.label public model_survey_label base.group_public 1 0 0 0
6 access_survey_user_input_public survey.user_input public model_survey_user_input base.group_public 1 1 1 0
7 access_survey_user_input_line_public survey.user_input_line public model_survey_user_input_line base.group_public 1 1 1 0
8 access_survey_stage_public survey.stage public model_survey_stage base.group_public 1 0 0 0
9 access_survey_user survey.survey user model_survey_survey base.group_survey_user 1 0 0 0
10 access_survey_page_user survey.page user model_survey_page base.group_survey_user 1 0 0 0
11 access_survey_question_user survey.question user model_survey_question base.group_survey_user 1 0 0 0

View File

@ -2596,6 +2596,7 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
this.$('.select_records').on('click', self.on_click); this.$('.select_records').on('click', self.on_click);
}, },
on_click: function(ev) { on_click: function(ev) {
event.preventDefault();
var self = this; var self = this;
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field); var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
this.pop = new instance.web.form.SelectCreatePopup(this); this.pop = new instance.web.form.SelectCreatePopup(this);
@ -2614,15 +2615,14 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
}); });
} }
else { else {
var domain = ["id", "in", element_ids]; var domain = [["id", "in", element_ids]];
var domain_done = $.Deferred().resolve(domain); var domain_done = $.Deferred().resolve(domain);
} }
$.when(domain_done).then(function (domain) { $.when(domain_done).then(function (domain) {
var domain = self.pop.dataset.domain.concat(domain || []); var domain = self.pop.dataset.domain.concat(domain || []);
self.set_value(JSON.stringify(domain)) self.set_value(domain);
}); });
}); });
event.preventDefault();
}, },
}); });
@ -3394,6 +3394,12 @@ instance.web.form.CompletionFieldMixin = {
classname: 'oe_m2o_dropdown_option' classname: 'oe_m2o_dropdown_option'
}); });
} }
else if (values.length == 0)
values.push({
label: _t("No results to show..."),
action: function() {},
classname: 'oe_m2o_dropdown_option'
});
return values; return values;
}); });
@ -4457,7 +4463,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
else else
return $.when(); return $.when();
}).done(function () { }).done(function () {
if (!self.o2m.options.reload_on_button) { var ds = self.o2m.dataset;
var cached_records = _.any([ds.to_create, ds.to_delete, ds.to_write], function(value) {
return value.length;
});
if (!self.o2m.options.reload_on_button && !cached_records) {
self.handle_button(name, id, callback); self.handle_button(name, id, callback);
}else { }else {
self.handle_button(name, id, function(){ self.handle_button(name, id, function(){

View File

@ -436,6 +436,16 @@ instance.web.ActionManager = instance.web.Widget.extend({
ir_actions_act_window: function (action, options) { ir_actions_act_window: function (action, options) {
var self = this; var self = this;
if (action.target === 'current'){
action.context['active_model'] = action.res_model;
if (action.res_id){
action.context['active_id'] = action.res_id;
action.context['active_ids'] = [action.res_id];
} else{
delete action.context['active_id'];
delete action.context['active_ids'];
}
}
return this.ir_actions_common({ return this.ir_actions_common({
widget: function () { return new instance.web.ViewManagerAction(self, action); }, widget: function () { return new instance.web.ViewManagerAction(self, action); },
action: action, action: action,

View File

@ -108,8 +108,11 @@ class Website(openerp.addons.web.controllers.main.Home):
locs = request.website.enumerate_pages() locs = request.website.enumerate_pages()
while True: while True:
start = pages * LOC_PER_SITEMAP start = pages * LOC_PER_SITEMAP
loc_slice = islice(locs, start, start + LOC_PER_SITEMAP) values = {
urls = iuv.render(cr, uid, 'website.sitemap_locs', dict(locs=loc_slice), context=context) 'locs': islice(locs, start, start + LOC_PER_SITEMAP),
'url_root': request.httprequest.url_root[:-1],
}
urls = iuv.render(cr, uid, 'website.sitemap_locs', values, context=context)
if urls.strip(): if urls.strip():
page = iuv.render(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context) page = iuv.render(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context)
if not first_page: if not first_page:
@ -155,22 +158,25 @@ class Website(openerp.addons.web.controllers.main.Home):
@http.route('/website/theme_change', type='http', auth="user", website=True) @http.route('/website/theme_change', type='http', auth="user", website=True)
def theme_change(self, theme_id=False, **kwargs): def theme_change(self, theme_id=False, **kwargs):
imd = request.registry['ir.model.data'] imd = request.registry['ir.model.data']
view = request.registry['ir.ui.view'] Views = request.registry['ir.ui.view']
view_model, view_option_id = imd.get_object_reference( _, theme_template_id = imd.get_object_reference(
request.cr, request.uid, 'website', 'theme') request.cr, request.uid, 'website', 'theme')
views = view.search( views = Views.search(request.cr, request.uid, [
request.cr, request.uid, [('inherit_id', '=', view_option_id)], ('inherit_id', '=', theme_template_id),
context=request.context) ('application', '=', 'enabled'),
view.write(request.cr, request.uid, views, {'inherit_id': False}, ], context=request.context)
context=request.context) Views.write(request.cr, request.uid, views, {
'application': 'disabled',
}, context=request.context)
if theme_id: if theme_id:
module, xml_id = theme_id.split('.') module, xml_id = theme_id.split('.')
view_model, view_id = imd.get_object_reference( _, view_id = imd.get_object_reference(
request.cr, request.uid, module, xml_id) request.cr, request.uid, module, xml_id)
view.write(request.cr, request.uid, [view_id], Views.write(request.cr, request.uid, [view_id], {
{'inherit_id': view_option_id}, context=request.context) 'application': 'enabled'
}, context=request.context)
return request.render('website.themes', {'theme_changed': True}) return request.render('website.themes', {'theme_changed': True})
@ -194,54 +200,45 @@ class Website(openerp.addons.web.controllers.main.Home):
module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context) module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
return request.redirect(redirect) return request.redirect(redirect)
@http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
def customize_template_set(self, view_id):
view_obj = request.registry.get("ir.ui.view")
view = view_obj.browse(request.cr, request.uid, int(view_id),
context=request.context)
if view.inherit_id:
value = False
else:
value = view.inherit_option_id and view.inherit_option_id.id or False
view_obj.write(request.cr, request.uid, [view_id], {
'inherit_id': value
}, context=request.context)
return True
@http.route('/website/customize_template_get', type='json', auth='user', website=True) @http.route('/website/customize_template_get', type='json', auth='user', website=True)
def customize_template_get(self, xml_id, optional=True): def customize_template_get(self, xml_id, full=False):
""" Lists the templates customizing ``xml_id``. By default, only
returns optional templates (which can be toggled on and off), if
``full=True`` returns all templates customizing ``xml_id``
"""
imd = request.registry['ir.model.data'] imd = request.registry['ir.model.data']
view_model, view_theme_id = imd.get_object_reference( view_model, view_theme_id = imd.get_object_reference(
request.cr, request.uid, 'website', 'theme') request.cr, request.uid, 'website', 'theme')
user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context) user = request.registry['res.users']\
group_ids = [g.id for g in user.groups_id] .browse(request.cr, request.uid, request.uid, request.context)
user_groups = set(user.groups_id)
view = request.registry.get("ir.ui.view") views = request.registry["ir.ui.view"]\
views = view._views_get(request.cr, request.uid, xml_id, context=request.context) ._views_get(request.cr, request.uid, xml_id, context=request.context)
done = {} done = set()
result = [] result = []
for v in views: for v in views:
if v.groups_id and [g for g in v.groups_id if g.id not in group_ids]: if not user_groups.issuperset(v.groups_id):
continue continue
if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional: if full or (v.application != 'always' and v.inherit_id.id != view_theme_id):
if v.inherit_option_id.id not in done: if v.inherit_id not in done:
result.append({ result.append({
'name': v.inherit_option_id.name, 'name': v.inherit_id.name,
'id': v.id, 'id': v.id,
'xml_id': v.xml_id, 'xml_id': v.xml_id,
'inherit_id': v.inherit_id.id, 'inherit_id': v.inherit_id.id,
'header': True, 'header': True,
'active': False 'active': False
}) })
done[v.inherit_option_id.id] = True done.add(v.inherit_id)
result.append({ result.append({
'name': v.name, 'name': v.name,
'id': v.id, 'id': v.id,
'xml_id': v.xml_id, 'xml_id': v.xml_id,
'inherit_id': v.inherit_id.id, 'inherit_id': v.inherit_id.id,
'header': False, 'header': False,
'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id) 'active': v.application in ('always', 'enabled'),
}) })
return result return result
@ -381,7 +378,7 @@ class Website(openerp.addons.web.controllers.main.Home):
""" """
response = werkzeug.wrappers.Response() response = werkzeug.wrappers.Response()
return request.registry['website']._image( return request.registry['website']._image(
request.cr, request.uid, model, id, field, response) request.cr, request.uid, model, id, field, response, max_width, max_height)
#------------------------------------------------------ #------------------------------------------------------

View File

@ -141,49 +141,50 @@ class ir_http(orm.AbstractModel):
return response return response
def _handle_exception(self, exception=None, code=500): def _handle_exception(self, exception=None, code=500):
if isinstance(exception, werkzeug.exceptions.HTTPException) and hasattr(exception, 'response') and exception.response: try:
return exception.response return super(ir_http, self)._handle_exception(exception)
except Exception:
attach = self._serve_attachment() attach = self._serve_attachment()
if attach: if attach:
return attach return attach
if getattr(request, 'website_enabled', False) and request.website: if getattr(request, 'website_enabled', False) and request.website:
values = dict( values = dict(
exception=exception, exception=exception,
traceback=traceback.format_exc(exception), traceback=traceback.format_exc(exception),
) )
if exception: if exception:
code = getattr(exception, 'code', code) code = getattr(exception, 'code', code)
if isinstance(exception, ir_qweb.QWebException): if isinstance(exception, ir_qweb.QWebException):
values.update(qweb_exception=exception) values.update(qweb_exception=exception)
if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError): if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
code = 403 code = 403
if code == 500: if code == 500:
logger.error("500 Internal Server Error:\n\n%s", values['traceback']) logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
if 'qweb_exception' in values: if 'qweb_exception' in values:
view = request.registry.get("ir.ui.view") view = request.registry.get("ir.ui.view")
views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context) views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
to_reset = [v for v in views if v.model_data_id.noupdate is True] to_reset = [v for v in views if v.model_data_id.noupdate is True]
values['views'] = to_reset values['views'] = to_reset
elif code == 403: elif code == 403:
logger.warn("403 Forbidden:\n\n%s", values['traceback']) logger.warn("403 Forbidden:\n\n%s", values['traceback'])
values.update( values.update(
status_message=werkzeug.http.HTTP_STATUS_CODES[code], status_message=werkzeug.http.HTTP_STATUS_CODES[code],
status_code=code, status_code=code,
) )
if not request.uid: if not request.uid:
self._auth_method_public() self._auth_method_public()
try: try:
html = request.website._render('website.%s' % code, values) html = request.website._render('website.%s' % code, values)
except Exception: except Exception:
html = request.website._render('website.http_error', values) html = request.website._render('website.http_error', values)
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
return super(ir_http, self)._handle_exception(exception) raise
class ModelConverter(ir.ir_http.ModelConverter): class ModelConverter(ir.ir_http.ModelConverter):
def __init__(self, url_map, model=False, domain='[]'): def __init__(self, url_map, model=False, domain='[]'):

View File

@ -15,6 +15,7 @@ import urllib2
import urlparse import urlparse
import re import re
import werkzeug.urls
import werkzeug.utils import werkzeug.utils
from dateutil import parser from dateutil import parser
from lxml import etree, html from lxml import etree, html
@ -265,10 +266,19 @@ class Image(orm.AbstractModel):
if options is None: options = {} if options is None: options = {}
classes = ['img', 'img-responsive'] + options.get('class', '').split() classes = ['img', 'img-responsive'] + options.get('class', '').split()
return ir_qweb.HTMLSafe('<img class="%s" src="/website/image?model=%s&field=%s&id=%s"/>' % ( url_params = {
'model': record._model._name,
'field': field_name,
'id': record.id,
}
for options_key in ['max_width', 'max_height']:
if options.get(options_key):
url_params[options_key] = options[options_key]
return ir_qweb.HTMLSafe('<img class="%s" src="/website/image?%s"/>' % (
' '.join(itertools.imap(werkzeug.utils.escape, classes)), ' '.join(itertools.imap(werkzeug.utils.escape, classes)),
record._model._name, werkzeug.urls.url_encode(url_params)
field_name, record.id)) ))
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$') local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
def from_html(self, cr, uid, model, column, element, context=None): def from_html(self, cr, uid, model, column, element, context=None):

View File

@ -13,8 +13,6 @@ from openerp.osv import osv, fields
class view(osv.osv): class view(osv.osv):
_inherit = "ir.ui.view" _inherit = "ir.ui.view"
_columns = { _columns = {
'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)"), '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_title': fields.char("Website meta title", size=70, translate=True),
'website_meta_description': fields.text("Website meta description", size=160, translate=True), 'website_meta_description': fields.text("Website meta description", size=160, translate=True),
@ -24,25 +22,30 @@ class view(osv.osv):
'page': False, 'page': False,
} }
def _view_obj(self, cr, uid, view_id, context=None):
if isinstance(view_id, basestring):
return self.pool['ir.model.data'].xmlid_to_object(
cr, uid, view_id, raise_if_not_found=True, context=context
)
elif isinstance(view_id, (int, long)):
return self.browse(cr, uid, view_id, context=context)
# assume it's already a view object (WTF?)
return view_id
# Returns all views (called and inherited) related to a view # Returns all views (called and inherited) related to a view
# Used by translation mechanism, SEO and optional templates # Used by translation mechanism, SEO and optional templates
def _views_get(self, cr, uid, view, options=True, context=None, root=True, stack_result=None): def _views_get(self, cr, uid, view_id, options=True, context=None, root=True):
if not context: """ For a given view ``view_id``, should return:
context = {}
if not stack_result:
stack_result = []
def view_obj(view):
if isinstance(view, basestring):
mod_obj = self.pool.get("ir.model.data")
m, n = view.split('.')
view = mod_obj.get_object(cr, uid, m, n, context=context)
elif isinstance(view, (int, long)):
view = self.pool.get("ir.ui.view").browse(cr, uid, view, context=context)
return view
* the view itself
* all views inheriting from it, enabled or not
- but not the optional children of a non-enabled child
* all views called from it (via t-call)
"""
try: try:
view = view_obj(view) view = self._view_obj(cr, uid, view_id, context=context)
except ValueError: except ValueError:
# Shall we log that ? # Shall we log that ?
return [] return []
@ -55,19 +58,25 @@ class view(osv.osv):
node = etree.fromstring(view.arch) node = etree.fromstring(view.arch)
for child in node.xpath("//t[@t-call]"): for child in node.xpath("//t[@t-call]"):
try: try:
call_view = view_obj(child.get('t-call')) called_view = self._view_obj(cr, uid, child.get('t-call'), context=context)
except ValueError: except ValueError:
continue continue
if call_view not in result: if called_view not in result:
result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result) result += self._views_get(cr, uid, called_view, options=options, context=context)
todo = view.inherit_children_ids extensions = view.inherit_children_ids
if options: if not options:
todo += filter(lambda x: not x.inherit_id, view.inherited_option_ids) # only active children
# Keep options in a determinitic order whatever their enabled disabled status extensions = (v for v in view.inherit_children_ids
todo.sort(lambda x,y:cmp(x.id,y.id)) if v.application in ('always', 'enabled'))
for child_view in todo:
for r in self._views_get(cr, uid, child_view, options=bool(child_view.inherit_id), context=context, root=False, stack_result=result): # Keep options in a deterministic order regardless of their applicability
for extension in sorted(extensions, key=lambda v: v.id):
for r in self._views_get(
cr, uid, extension,
# only return optional grandchildren if this child is enabled
options=extension.application in ('always', 'enabled'),
context=context, root=False):
if r not in result: if r not in result:
result.append(r) result.append(r)
return result return result

View File

@ -541,7 +541,10 @@ class website(osv.osv):
response.mimetype = Image.MIME[image.format] response.mimetype = Image.MIME[image.format]
w, h = image.size w, h = image.size
max_w, max_h = int(max_width), int(max_height) try:
max_w, max_h = int(max_width), int(max_height)
except:
max_w, max_h = (maxint, maxint)
if w < max_w and h < max_h: if w < max_w and h < max_h:
response.data = data response.data = data

View File

@ -97,7 +97,7 @@
var viewId = $(document.documentElement).data('view-xmlid'); var viewId = $(document.documentElement).data('view-xmlid');
openerp.jsonRpc('/website/customize_template_get', 'call', { openerp.jsonRpc('/website/customize_template_get', 'call', {
'xml_id': viewId, 'xml_id': viewId,
'optional': false, 'full': true,
}).then(function (views) { }).then(function (views) {
self.loadViews.call(self, views); self.loadViews.call(self, views);
self.open.call(self); self.open.call(self);

View File

@ -470,8 +470,14 @@
}); });
menu.on('click', 'a[data-action!=ace]', function (event) { menu.on('click', 'a[data-action!=ace]', function (event) {
var view_id = $(event.currentTarget).data('view-id'); var view_id = $(event.currentTarget).data('view-id');
openerp.jsonRpc('/website/customize_template_toggle', 'call', { return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
'view_id': view_id model: 'ir.ui.view',
method: 'toggle',
args: [],
kwargs: {
ids: [parseInt(view_id, 10)],
context: website.get_context()
}
}).then( function() { }).then( function() {
window.location.reload(); window.location.reload();
}); });

View File

@ -264,21 +264,21 @@
}, },
description: function () { description: function () {
var $description = $('meta[name=description]'); var $description = $('meta[name=description]');
return ($description.length > 0) && ($description.attr('value') && $description.attr('value').trim()); return ($description.length > 0) && ($description.attr('content') && $description.attr('content').trim());
}, },
changeDescription: function (description) { changeDescription: function (description) {
// TODO create tag if missing // TODO create tag if missing
$('meta[name=description]').attr('value', description); $('meta[name=description]').attr('content', description);
this.trigger('description-changed', description); this.trigger('description-changed', description);
}, },
keywords: function () { keywords: function () {
var $keywords = $('meta[name=keywords]'); var $keywords = $('meta[name=keywords]');
var parsed = ($keywords.length > 0) && $keywords.attr('value') && $keywords.attr('value').split(","); var parsed = ($keywords.length > 0) && $keywords.attr('content') && $keywords.attr('content').split(",");
return (parsed && parsed[0]) ? parsed: []; return (parsed && parsed[0]) ? parsed: [];
}, },
changeKeywords: function (keywords) { changeKeywords: function (keywords) {
// TODO create tag if missing // TODO create tag if missing
$('meta[name=keywords]').attr('value', keywords.join(",")); $('meta[name=keywords]').attr('content', keywords.join(","));
this.trigger('keywords-changed', keywords); this.trigger('keywords-changed', keywords);
}, },
headers: function (tag) { headers: function (tag) {
@ -296,7 +296,7 @@
}); });
}, },
company: function () { company: function () {
return $('meta[name="openerp.company"]').attr('value'); return $('html').attr('data-oe-company-name');
}, },
bodyText: function () { bodyText: function () {
return $('body').children().not('.js_seo_configuration').text(); return $('body').children().not('.js_seo_configuration').text();

View File

@ -203,82 +203,82 @@
All Default Themes All Default Themes
--> -->
<template id="website.theme_amelia" name="Amelia" inherit_option_id="website.theme"> <template id="website.theme_amelia" name="Amelia" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_cerulean" name="Cerulean" inherit_option_id="website.theme"> <template id="website.theme_cerulean" name="Cerulean" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cerulean.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/cerulean.min.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_cosmo" name="Cosmo" inherit_option_id="website.theme"> <template id="website.theme_cosmo" name="Cosmo" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_cyborg" name="Cyborg" inherit_option_id="website.theme"> <template id="website.theme_cyborg" name="Cyborg" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_flatly" name="Flatly" inherit_option_id="website.theme"> <template id="website.theme_flatly" name="Flatly" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_journal" name="Journal" inherit_option_id="website.theme"> <template id="website.theme_journal" name="Journal" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_readable" name="Readable" inherit_option_id="website.theme"> <template id="website.theme_readable" name="Readable" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_simplex" name="Simplex" inherit_option_id="website.theme"> <template id="website.theme_simplex" name="Simplex" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_slate" name="Slate" inherit_option_id="website.theme"> <template id="website.theme_slate" name="Slate" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_spacelab" name="Spacelab" inherit_option_id="website.theme"> <template id="website.theme_spacelab" name="Spacelab" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.fix.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_united" name="United" inherit_option_id="website.theme"> <template id="website.theme_united" name="United" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/united.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/united.min.css' t-ignore="true"/>
</xpath> </xpath>
</template> </template>
<template id="website.theme_yeti" name="Yeti" inherit_option_id="website.theme"> <template id="website.theme_yeti" name="Yeti" inherit_id="website.theme" optional="disabled">
<xpath expr="//link[@id='bootstrap_css']" position="replace"> <xpath expr="//link[@id='bootstrap_css']" position="replace">
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.min.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.min.css' t-ignore="true"/>
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.fix.css' t-ignore="true"/> <link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.fix.css' t-ignore="true"/>

View File

@ -61,8 +61,10 @@
t-att-data-editable="'1' if editable else None" t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable 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"> t-att-data-main-object="repr(main_object) if editable else None"
t-att-data-oe-company-name="res_company.name">
<head> <head>
<meta charset="utf-8" />
<t t-if="main_object and 'website_meta_title' in main_object"> <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-set="title" t-value="main_object.website_meta_title"/>
</t> </t>
@ -73,10 +75,9 @@
<t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t> <t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t>
</t> </t>
<meta name="viewport" content="initial-scale=1"/> <meta name="viewport" content="initial-scale=1"/>
<meta name="openerp.company" t-att-value="res_company.name"/> <meta name="description" t-att-content="main_object and 'website_meta_description' in main_object
<meta name="description" t-att-value="main_object and 'website_meta_description' in main_object
and main_object.website_meta_description or website_meta_description"/> and main_object.website_meta_description or website_meta_description"/>
<meta name="keywords" t-att-value="main_object and 'website_meta_keywords' in main_object <meta name="keywords" t-att-content="main_object and 'website_meta_keywords' in main_object
and main_object.website_meta_keywords or website_meta_keywords"/> and main_object.website_meta_keywords or website_meta_keywords"/>
<title><t t-esc="title"/></title> <title><t t-esc="title"/></title>
@ -137,14 +138,14 @@
<footer> <footer>
<div class="container hidden-print" id="footer_container"> <div class="container hidden-print" id="footer_container">
<div class="row"> <div class="row">
<div class="col-md-3" name="product"> <div class="col-md-3">
<h4>Our products &amp; Services</h4> <h4>Our products &amp; Services</h4>
<ul class="list-unstyled" name="products"> <ul class="list-unstyled" name="products">
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
</ul> </ul>
</div> </div>
<div class="col-md-3" name="info"> <div class="col-md-3" name="info">
<h4 name="info_title">Connect with us</h4> <h4>Connect with us</h4>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li><a href="/page/website.contactus">Contact us</a></li> <li><a href="/page/website.contactus">Contact us</a></li>
</ul> </ul>
@ -161,7 +162,7 @@
<a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a> <a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
</h2> </h2>
</div> </div>
<div class="col-md-5 col-lg-offset-1" name="about_us"> <div class="col-md-5 col-lg-offset-1">
<div> <div>
<h4> <h4>
<span t-field="res_company.name">Your Company</span> <span t-field="res_company.name">Your Company</span>
@ -220,7 +221,7 @@
</html> </html>
</template> </template>
<template id="layout_logo_show" inherit_id="website.layout" inherit_option_id="website.layout" name="Show Logo"> <template id="layout_logo_show" inherit_id="website.layout" optional="enabled" name="Show Logo">
<xpath expr="//header//a[@class='navbar-brand']" position="replace"> <xpath expr="//header//a[@class='navbar-brand']" position="replace">
<a href="/" class="navbar-brand logo"> <a href="/" class="navbar-brand logo">
<img src="/logo.png"/> <img src="/logo.png"/>
@ -304,9 +305,9 @@
<script type="text/javascript" src="/website/static/src/js/jQuery.transfo.js"></script> <script type="text/javascript" src="/website/static/src/js/jQuery.transfo.js"></script>
</template> </template>
<template id="debugger" inherit_option_id="website.layout" name="Debugger &amp; Tests"> <template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger &amp; Tests">
<xpath expr='//t[@name="layout_head"]' position="after"> <xpath expr='//t[@name="layout_head"]' position="after">
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script> <t t-set="debugger_hook" t-value="1" />
</xpath> </xpath>
</template> </template>
@ -318,7 +319,7 @@
</xpath> </xpath>
</template> </template>
<template id="show_sign_in" inherit_option_id="website.layout" inherit_id="website.layout" name="Show Sign In" groups="base.group_public"> <template id="show_sign_in" optional="enabled" inherit_id="website.layout" name="Show Sign In" groups="base.group_public">
<xpath expr="//ul[@id='top_menu']" position="inside"> <xpath expr="//ul[@id='top_menu']" position="inside">
<li class="divider"/> <li class="divider"/>
<li> <li>
@ -329,7 +330,7 @@
</xpath> </xpath>
</template> </template>
<template id="footer_custom" inherit_option_id="website.layout" name="Custom Footer"> <template id="footer_custom" inherit_id="website.layout" optional="disabled" name="Custom Footer">
<xpath expr="//div[@id='footer_container']" position="before"> <xpath expr="//div[@id='footer_container']" position="before">
<div class="oe_structure"> <div class="oe_structure">
<section data-snippet-id='three-columns' class="mt16 mb16"> <section data-snippet-id='three-columns' class="mt16 mb16">

View File

@ -161,7 +161,7 @@
<!-- Option: Blog Post List: show tags --> <!-- Option: Blog Post List: show tags -->
<template id="opt_blog_post_short_tags" name="Tags" <template id="opt_blog_post_short_tags" name="Tags"
inherit_option_id="website_blog.blog_post_short" inherit_id="website_blog.blog_post_short"> optional="enabled" inherit_id="website_blog.blog_post_short">
<xpath expr="//div[@name='blog_post_data']" position="inside"> <xpath expr="//div[@name='blog_post_data']" position="inside">
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)"> <p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="fa fa-tags"/> <span class="fa fa-tags"/>
@ -265,7 +265,7 @@
<!-- Options: Blog Post: breadcrumb --> <!-- Options: Blog Post: breadcrumb -->
<template id="blog_breadcrumb" name="Breadcrumb" <template id="blog_breadcrumb" name="Breadcrumb"
inherit_option_id="website_blog.blog_post_complete"> inherit_id="website_blog.blog_post_complete" optional="disabled">
<xpath expr="//div[@id='title']" position="before"> <xpath expr="//div[@id='title']" position="before">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@ -285,7 +285,7 @@
<!-- Options: Blog Post: user can reply --> <!-- Options: Blog Post: user can reply -->
<template id="opt_blog_post_complete_comment" name="Allow blog post comment" <template id="opt_blog_post_complete_comment" name="Allow blog post comment"
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete" optional="disabled"
groups="website_mail.group_comment"> groups="website_mail.group_comment">
<xpath expr="//ul[@id='comments-list']" position="before"> <xpath expr="//ul[@id='comments-list']" position="before">
<section class="mb32 read_width css_editable_mode_hidden"> <section class="mb32 read_width css_editable_mode_hidden">
@ -307,7 +307,7 @@
<!-- Options: Blog Post: user can select text for tweet --> <!-- Options: Blog Post: user can select text for tweet -->
<template id="opt_blog_post_select_to_tweet" name="Select to Tweet" <template id="opt_blog_post_select_to_tweet" name="Select to Tweet"
inherit_option_id="website_blog.blog_post_complete"> inherit_id="website_blog.blog_post_complete" optional="disabled">
<xpath expr="//div[@id='blog_content']" position="attributes"> <xpath expr="//div[@id='blog_content']" position="attributes">
<attribute name="class">js_tweet mt32</attribute> <attribute name="class">js_tweet mt32</attribute>
</xpath> </xpath>
@ -318,7 +318,7 @@
<!-- Options: Blog Post: user can add Inline Discussion --> <!-- Options: Blog Post: user can add Inline Discussion -->
<template id="opt_blog_post_inline_discussion" name="Allow comment in text" <template id="opt_blog_post_inline_discussion" name="Allow comment in text"
inherit_option_id="website_blog.blog_post_complete"> inherit_id="website_blog.blog_post_complete" optional="disabled">
<xpath expr="//div[@id='blog_content']" position="attributes"> <xpath expr="//div[@id='blog_content']" position="attributes">
<attribute name="enable_chatter_discuss">True</attribute> <attribute name="enable_chatter_discuss">True</attribute>
</xpath> </xpath>
@ -326,7 +326,7 @@
<!-- Options: Blog Post: show tags --> <!-- Options: Blog Post: show tags -->
<template id="opt_blog_post_complete_tags" name="Tags" <template id="opt_blog_post_complete_tags" name="Tags"
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete"> optional="enabled" inherit_id="website_blog.blog_post_complete">
<xpath expr="//p[@name='blog_post_data']" position="after"> <xpath expr="//p[@name='blog_post_data']" position="after">
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)"> <p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="fa fa-tags"/> <span class="fa fa-tags"/>
@ -355,7 +355,7 @@
<!-- Option:Right Column for extra info --> <!-- Option:Right Column for extra info -->
<template id="index_right" name="Right Column" <template id="index_right" name="Right Column"
inherit_option_id="website_blog.blog_post_short"> inherit_id="website_blog.blog_post_short" optional="disabled">
<xpath expr="//div[@id='main_column']" position="attributes"> <xpath expr="//div[@id='main_column']" position="attributes">
<attribute name="class">col-sm-8</attribute> <attribute name="class">col-sm-8</attribute>
</xpath> </xpath>
@ -366,7 +366,7 @@
<!-- Option:Right Column: tags --> <!-- Option:Right Column: tags -->
<template id="opt_blog_rc_tags" name="Tags" <template id="opt_blog_rc_tags" name="Tags"
inherit_option_id="website_blog.index_right"> inherit_id="website_blog.index_right" optional="disabled">
<xpath expr="//div[@id='blog_right_column']" position="inside"> <xpath expr="//div[@id='blog_right_column']" position="inside">
<section class="mt32"> <section class="mt32">
<h4>Tags</h4> <h4>Tags</h4>
@ -383,7 +383,7 @@
<!-- Option:Right Column: archives --> <!-- Option:Right Column: archives -->
<template id="opt_blog_rc_history" name="Archives" <template id="opt_blog_rc_history" name="Archives"
inherit_option_id="website_blog.index_right"> inherit_id="website_blog.index_right" optional="disabled">
<xpath expr="//div[@id='blog_right_column']" position="inside"> <xpath expr="//div[@id='blog_right_column']" position="inside">
<section class="mt32"> <section class="mt32">
<h4>Archives</h4> <h4>Archives</h4>
@ -400,7 +400,7 @@
<!-- Option:Right Column: about us --> <!-- Option:Right Column: about us -->
<template id="opt_blog_rc_about_us" name="About Us" priority="2" <template id="opt_blog_rc_about_us" name="About Us" priority="2"
inherit_option_id="website_blog.index_right"> inherit_id="website_blog.index_right" optional="disabled">
<xpath expr="//div[@id='blog_right_column']" position="inside"> <xpath expr="//div[@id='blog_right_column']" position="inside">
<section class="mt32"> <section class="mt32">
<h4>About us</h4> <h4>About us</h4>
@ -417,7 +417,7 @@
<!-- Option:Right Column: follow us --> <!-- Option:Right Column: follow us -->
<template id="opt_blog_rc_follow_us" name="Follow us" priority="4" <template id="opt_blog_rc_follow_us" name="Follow us" priority="4"
inherit_option_id="website_blog.index_right"> inherit_id="website_blog.index_right" optional="disabled">
<xpath expr="//div[@id='blog_right_column']" position="inside"> <xpath expr="//div[@id='blog_right_column']" position="inside">
<section class="mt32"> <section class="mt32">
<h4>Follow us<small t-if="blog">: <t t-esc="blog.name"/></small></h4> <h4>Follow us<small t-if="blog">: <t t-esc="blog.name"/></small></h4>
@ -444,7 +444,7 @@
<!-- Option:Right Column: blogs --> <!-- Option:Right Column: blogs -->
<template id="opt_blog_rc_blogs" name="Our Blogs" priority="6" <template id="opt_blog_rc_blogs" name="Our Blogs" priority="6"
inherit_option_id="website_blog.index_right"> inherit_id="website_blog.index_right" optional="disabled">
<xpath expr="//div[@id='blog_right_column']" position="inside"> <xpath expr="//div[@id='blog_right_column']" position="inside">
<section class="mt32 mb32"> <section class="mt32 mb32">
<h4>Our Blogs</h4> <h4>Our Blogs</h4>

View File

@ -2,7 +2,7 @@
<openerp> <openerp>
<data> <data>
<template id="contactus_form" name="Contact Form" inherit_id="website.contactus" inherit_option_id="website.contactus"> <template id="contactus_form" name="Contact Form" inherit_id="website.contactus" optional="enabled">
<xpath expr="//div[@name='mail_button']" position="replace"> <xpath expr="//div[@name='mail_button']" position="replace">
<form action="/crm/contactus" method="post" class="form-horizontal mt32" enctype="multipart/form-data"> <form action="/crm/contactus" method="post" class="form-horizontal mt32" enctype="multipart/form-data">
<t t-foreach="kwargs" t-as="kwarg"> <t t-foreach="kwargs" t-as="kwarg">
@ -47,7 +47,7 @@
</xpath> </xpath>
</template> </template>
<template id="contactus_form_company_name" name="Company Name" inherit_id="website_crm.contactus_form" inherit_option_id="website_crm.contactus_form"> <template id="contactus_form_company_name" name="Company Name" inherit_id="website_crm.contactus_form" optional="enabled">
<xpath expr="//div[@name='email_from_container']" position="after"> <xpath expr="//div[@name='email_from_container']" position="after">
<div t-attf-class="form-group #{error and 'partner_name' in error and 'has-error' or ''}"> <div t-attf-class="form-group #{error and 'partner_name' in error and 'has-error' or ''}">
<label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Company</label> <label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Company</label>

View File

@ -86,8 +86,8 @@
</t> </t>
<div class="media"> <div class="media">
<a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}" <a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"
t-field="partner.image_small" t-field="partner.image"
t-field-options='{"widget": "image", "class": "media-object"}' t-field-options='{"widget": "image", "class": "media-object", "max_width": 128}'
></a> ></a>
<div class="media-body o_partner_body" style="min-height: 64px;"> <div class="media-body o_partner_body" style="min-height: 64px;">
<a class="media-heading" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"> <a class="media-heading" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}">
@ -113,7 +113,7 @@
</t> </t>
</template> </template>
<template id="ref_country" inherit_id="website_crm_partner_assign.index" inherit_option_id="website_crm_partner_assign.index" name="Left World Map"> <template id="ref_country" inherit_id="website_crm_partner_assign.index" optional="enabled" name="Left World Map">
<xpath expr="//ul[@id='reseller_countries']" position="after"> <xpath expr="//ul[@id='reseller_countries']" position="after">
<h3>World Map</h3> <h3>World Map</h3>
<ul class="nav"> <ul class="nav">

View File

@ -66,7 +66,7 @@
</template> </template>
<!-- Option: left column: World Map --> <!-- Option: left column: World Map -->
<template id="opt_country" inherit_option_id="website_customer.index" name="Show Map"> <template id="opt_country" inherit_id="website_customer.index" optional="disabled" name="Show Map">
<xpath expr="//div[@id='ref_left_column']" position="inside"> <xpath expr="//div[@id='ref_left_column']" position="inside">
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/&amp;output=embed" <iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/&amp;output=embed"
@ -74,7 +74,7 @@
</xpath> </xpath>
</template> </template>
<template id="opt_country_list" inherit_id="website_customer.index" inherit_option_id="website_customer.index" name="Filter on Countries"> <template id="opt_country_list" inherit_id="website_customer.index" optional="enabled" name="Filter on Countries">
<xpath expr="//div[@id='ref_left_column']" position="inside"> <xpath expr="//div[@id='ref_left_column']" position="inside">
<h3>References by Country</h3> <h3>References by Country</h3>
<ul class="nav nav-pills nav-stacked mt16 mb32"> <ul class="nav nav-pills nav-stacked mt16 mb32">

View File

@ -87,7 +87,7 @@
</t> </t>
</template> </template>
<template id="event_right_photos" inherit_option_id="website_event.index" name="Photos"> <template id="event_right_photos" inherit_id="website_event.index" optional="disabled" name="Photos">
<xpath expr="//div[@id='right_column']" position="inside"> <xpath expr="//div[@id='right_column']" position="inside">
<div class="row"> <div class="row">
<div class="col-md-12 mb16"> <div class="col-md-12 mb16">
@ -106,7 +106,7 @@
</xpath> </xpath>
</template> </template>
<template id="event_right_quotes" inherit_option_id="website_event.index" name="Quotes"> <template id="event_right_quotes" inherit_id="website_event.index" optional="disabled" name="Quotes">
<xpath expr="//div[@id='right_column']" position="inside"> <xpath expr="//div[@id='right_column']" position="inside">
<div class="row"> <div class="row">
<div class="col-md-12 mb16"> <div class="col-md-12 mb16">
@ -123,7 +123,7 @@
</xpath> </xpath>
</template> </template>
<template id="event_right_country_event" inherit_option_id="website_event.index" name="Country Events"> <template id="event_right_country_event" inherit_id="website_event.index" optional="disabled" name="Country Events">
<xpath expr="//div[@id='right_column']" position="inside"> <xpath expr="//div[@id='right_column']" position="inside">
<div class="row"> <div class="row">
<div class="col-md-12 mb16 mt16 country_events"> <div class="col-md-12 mb16 mt16 country_events">
@ -140,7 +140,7 @@
</xpath> </xpath>
</template> </template>
<template id="event_left_column" inherit_option_id="website_event.index" inherit_id="website_event.index" name="Filters"> <template id="event_left_column" optional="enabled" inherit_id="website_event.index" name="Filters">
<xpath expr="//div[@id='middle_column']" position="attributes"> <xpath expr="//div[@id='middle_column']" position="attributes">
<attribute name="class">col-md-6</attribute> <attribute name="class">col-md-6</attribute>
</xpath> </xpath>
@ -159,7 +159,7 @@
</xpath> </xpath>
</template> </template>
<template id="event_category" inherit_option_id="website_event.event_left_column" name="Filter by Category"> <template id="event_category" inherit_id="website_event.event_left_column" optional="disabled" name="Filter by Category">
<xpath expr="//div[@id='left_column']" position="inside"> <xpath expr="//div[@id='left_column']" position="inside">
<ul class="nav nav-pills nav-stacked mt32"> <ul class="nav nav-pills nav-stacked mt32">
<t t-foreach="types"> <t t-foreach="types">
@ -173,7 +173,7 @@
</xpath> </xpath>
</template> </template>
<template id="event_location" inherit_option_id="website_event.event_left_column" name="Filter by Country"> <template id="event_location" inherit_id="website_event.event_left_column" optional="disabled" name="Filter by Country">
<xpath expr="//div[@id='left_column']" position="inside"> <xpath expr="//div[@id='left_column']" position="inside">
<ul class="nav nav-pills nav-stacked mt32"> <ul class="nav nav-pills nav-stacked mt32">
<t t-foreach="countries"> <t t-foreach="countries">

View File

@ -53,7 +53,7 @@
{ {
title: "Pay Now", title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])', waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible', element: '.oe_sale_acquirer_button .btn[type="submit"]:visible',
}, },
{ {
title: "finish", title: "finish",

View File

@ -3,7 +3,7 @@
<data> <data>
<template id="debugger" inherit_id="website.debugger" name="Event Debugger"> <template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr="//script[last()]" position="after"> <xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script> <script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script>
</xpath> </xpath>
</template> </template>
@ -26,7 +26,7 @@
</xpath> </xpath>
</template> </template>
<template id="event_description_full" inherit_id="website_event.event_description_full" inherit_option_id="website_event.event_description_full" name="Event's Ticket form"> <template id="event_description_full" inherit_id="website_event.event_description_full" optional="enabled" name="Event's Ticket form">
<xpath expr="//div[@t-field='event.description']" position="before"> <xpath expr="//div[@t-field='event.description']" position="before">
<form t-attf-action="/event/cart/update?event_id=#{ event.id }" method="post" t-if="event.event_ticket_ids"> <form t-attf-action="/event/cart/update?event_id=#{ event.id }" method="post" t-if="event.event_ticket_ids">
<table itemprop="offers" class="table table-striped"> <table itemprop="offers" class="table table-striped">

View File

@ -88,10 +88,21 @@ class website_event(http.Controller):
days_tracks_count[day] = len(tracks) days_tracks_count[day] = len(tracks)
days[day] = self._prepare_calendar(event, tracks) days[day] = self._prepare_calendar(event, tracks)
cr, uid, context = request.cr, request.uid, request.context
track_obj = request.registry['event.track']
tracks_ids = track_obj.search(cr, openerp.SUPERUSER_ID, [('event_id', '=', event.id)], context=context)
speakers = dict()
for t in track_obj.browse(cr, openerp.SUPERUSER_ID, tracks_ids, context=context):
acc = ""
for speaker in t.speaker_ids:
acc = speaker.name + u" " + acc if acc else speaker.name
speakers[t.id] = acc
return request.website.render("website_event_track.agenda", { return request.website.render("website_event_track.agenda", {
'event': event, 'event': event,
'days': days, 'days': days,
'days_nbr': days_tracks_count, 'days_nbr': days_tracks_count,
'speakers': speakers,
'tag': tag 'tag': tag
}) })

View File

@ -2,7 +2,7 @@
<openerp> <openerp>
<data> <data>
<template name="Sponsors" id="event_sponsor" inherit_option_id="website_event.layout" inherit_id="website_event.layout"> <template name="Sponsors" id="event_sponsor" optional="enabled" inherit_id="website_event.layout">
<xpath expr="//t[@t-call='website.layout']" position="inside"> <xpath expr="//t[@t-call='website.layout']" position="inside">
<t t-set="head"> <t t-set="head">
<link rel='stylesheet' href='/website_event_track/static/src/css/website_event_track.css'/> <link rel='stylesheet' href='/website_event_track/static/src/css/website_event_track.css'/>
@ -85,8 +85,8 @@
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }"> <a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
<span t-esc="track and track.name"/> <span t-esc="track and track.name"/>
</a> </a>
<div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker"> <div class="text-muted">
<small t-esc="speaker.display_name"/> <small t-esc="speakers[track.id]"/>
</div> </div>
</t> </t>
</td> </td>
@ -98,11 +98,11 @@
<t t-set="track" t-value="dt[1][False][0]"/> <t t-set="track" t-value="dt[1][False][0]"/>
<td t-att-colspan="len(locations)-1" t-attf-class="text-center event_color_#{track.color} #{track and 'event_track' or ''}"> <td t-att-colspan="len(locations)-1" t-attf-class="text-center event_color_#{track.color} #{track and 'event_track' or ''}">
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }"> <a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
<span t-esc="track.name"/><br/> <span t-esc="track.name"/>
<div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker">
<small t-esc="speaker.display_name"/>
</div>
</a> </a>
<div class="text-muted">
<small t-esc="speakers[track.id]"/>
</div>
</td> </td>
</t> </t>
</tr> </tr>
@ -157,7 +157,7 @@
</t> </t>
</template> </template>
<template id="tracks_filter" inherit_id="website_event_track.tracks" inherit_option_id="website_event_track.tracks" name="Filter on Tags"> <template id="tracks_filter" inherit_id="website_event_track.tracks" optional="enabled" name="Filter on Tags">
<xpath expr="//div[@id='left_column']" position="inside"> <xpath expr="//div[@id='left_column']" position="inside">
<ul class="nav nav-pills nav-stacked"> <ul class="nav nav-pills nav-stacked">
<li t-att-class="'' if searches.get('tag') else 'active'"><a t-attf-href="/event/#{ slug(event) }/track">All Tags</a></li> <li t-att-class="'' if searches.get('tag') else 'active'"><a t-attf-href="/event/#{ slug(event) }/track">All Tags</a></li>
@ -246,7 +246,7 @@
</t> </t>
</template> </template>
<template id="event_track_social" name="Social Widgets" inherit_option_id="website_event_track.track_view"> <template id="event_track_social" name="Social Widgets" inherit_id="website_event_track.track_view" optional="disabled">
<xpath expr="//div[@id='right_column']" position="inside"> <xpath expr="//div[@id='right_column']" position="inside">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">

View File

@ -330,13 +330,12 @@ class WebsiteForum(http.Controller):
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
if kwargs.get('comment') and post.forum_id.id == forum.id: if kwargs.get('comment') and post.forum_id.id == forum.id:
# TDE FIXME: check that post_id is the question or one of its answers # TDE FIXME: check that post_id is the question or one of its answers
if request.registry['res.users'].has_group(cr, uid, 'website_mail.group_comment'): request.registry['forum.post'].message_post(
request.registry['forum.post'].message_post( cr, uid, post.id,
cr, uid, post.id, body=kwargs.get('comment'),
body=kwargs.get('comment'), type='comment',
type='comment', subtype='mt_comment',
subtype='mt_comment', context=dict(context, mail_create_nosubcribe=True))
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question))) return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True) @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
@ -449,7 +448,10 @@ class WebsiteForum(http.Controller):
pager = request.website.pager(url="/forum/%s/users" % slug(forum), total=tag_count, page=page, step=step, scope=30) pager = request.website.pager(url="/forum/%s/users" % slug(forum), total=tag_count, page=page, step=step, scope=30)
obj_ids = User.search(cr, SUPERUSER_ID, [('karma', '>', 1)], limit=step, offset=pager['offset'], order='karma DESC', context=context) obj_ids = User.search(cr, SUPERUSER_ID, [('karma', '>', 1)], limit=step, offset=pager['offset'], order='karma DESC', context=context)
users = User.browse(cr, SUPERUSER_ID, obj_ids, context=context) # put the users in block of 3 to display them as a table
users = [[] for i in range(len(obj_ids)/3+1)]
for index, user in enumerate(User.browse(cr, SUPERUSER_ID, obj_ids, context=context)):
users[index/3].append(user)
searches['users'] = 'True' searches['users'] = 'True'
values = self._prepare_forum_values(forum=forum, searches=searches) values = self._prepare_forum_values(forum=forum, searches=searches)

View File

@ -725,11 +725,8 @@
<template id="users"> <template id="users">
<t t-call="website_forum.header"> <t t-call="website_forum.header">
<t t-foreach="users" t-as="user"> <div t-foreach="users" t-as="row_users" class="row mt16">
<t t-if="user_index % 3 == 0"> <div t-foreach="row_users" t-as="user" class="col-sm-4">
<div class="row"></div>
</t>
<div class="col-sm-4">
<img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{user.id}/avatar"/> <img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{user.id}/avatar"/>
<div> <div>
<a t-attf-href="/forum/#{slug(forum)}/user/#{user.id}" t-field="user.name"/> <a t-attf-href="/forum/#{slug(forum)}/user/#{user.id}" t-field="user.name"/>
@ -750,7 +747,7 @@
<t t-raw="0"/> <t t-raw="0"/>
</div> </div>
</div> </div>
</t> </div>
<div class="pull-left"> <div class="pull-left">
<t t-call="website.pager"/> <t t-call="website.pager"/>
</div> </div>

View File

@ -3,7 +3,7 @@
<data> <data>
<!-- Page --> <!-- Page -->
<template id="aboutus" inherit_id="website.aboutus" inherit_option_id="website.aboutus" name="Our Team"> <template id="aboutus" inherit_id="website.aboutus" optional="enabled" name="Our Team">
<xpath expr="//div[@class='oe_structure']" position="after"> <xpath expr="//div[@class='oe_structure']" position="after">
<section class="container"> <section class="container">
<div class="col-sm-12 text-center" t-if="len(employee_ids)"> <div class="col-sm-12 text-center" t-if="len(employee_ids)">

View File

@ -11,6 +11,26 @@
<field name="perm_create" eval="False"/> <field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/> <field name="perm_unlink" eval="False"/>
</record> </record>
<record id="hr_job_portal" model="ir.rule">
<field name="name">Job Positions: Portal</field>
<field name="model_id" ref="hr.model_hr_job"/>
<field name="domain_force">[('website_published', '=', True)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="hr_job_officer" model="ir.rule">
<field name="name">Job Positions: HR Officer</field>
<field name="model_id" ref="hr.model_hr_job"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_hr_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="hr_department_public" model="ir.rule"> <record id="hr_department_public" model="ir.rule">
<field name="name">Job department: Public</field> <field name="name">Job department: Public</field>
<field name="model_id" ref="hr.model_hr_department"/> <field name="model_id" ref="hr.model_hr_department"/>

View File

@ -230,7 +230,7 @@
</t> </t>
</template> </template>
<template id="job_departments" inherit_option_id="website_hr_recruitment.index" name="Filter by Departments"> <template id="job_departments" inherit_id="website_hr_recruitment.index" optional="disabled" name="Filter by Departments">
<xpath expr="//div[@id='jobs_grid_left']" position="inside"> <xpath expr="//div[@id='jobs_grid_left']" position="inside">
<ul class="nav nav-pills nav-stacked mb32"> <ul class="nav nav-pills nav-stacked mb32">
<li t-att-class=" '' if department_id else 'active' "><a href="/jobs">All Departments</a></li> <li t-att-class=" '' if department_id else 'active' "><a href="/jobs">All Departments</a></li>
@ -249,7 +249,7 @@
</xpath> </xpath>
</template> </template>
<template id="job_offices" inherit_option_id="website_hr_recruitment.index" name="Filter by Offices"> <template id="job_offices" inherit_id="website_hr_recruitment.index" optional="disabled" name="Filter by Offices">
<xpath expr="//div[@id='jobs_grid_left']" position="inside"> <xpath expr="//div[@id='jobs_grid_left']" position="inside">
<ul class="nav nav-pills nav-stacked mb32"> <ul class="nav nav-pills nav-stacked mb32">
<li t-att-class=" '' if office_id else 'active' "><a href="/jobs">All Offices</a></li> <li t-att-class=" '' if office_id else 'active' "><a href="/jobs">All Offices</a></li>

View File

@ -62,6 +62,7 @@ class WebsiteMail(http.Controller):
@http.route(['/website_mail/is_follower'], type='json', auth="public", website=True) @http.route(['/website_mail/is_follower'], type='json', auth="public", website=True)
def call(self, model, id, **post): def call(self, model, id, **post):
id = int(id)
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry.get('res.partner') partner_obj = request.registry.get('res.partner')
@ -74,17 +75,28 @@ class WebsiteMail(http.Controller):
partner_id = users_obj.browse(cr, SUPERUSER_ID, uid, context).partner_id partner_id = users_obj.browse(cr, SUPERUSER_ID, uid, context).partner_id
elif request.session.get('partner_id'): elif request.session.get('partner_id'):
partner_id = partner_obj.browse(cr, SUPERUSER_ID, request.session.get('partner_id'), context) partner_id = partner_obj.browse(cr, SUPERUSER_ID, request.session.get('partner_id'), context)
email = partner_id and partner_id.email or ""
email = ""
is_follower = False
if partner_id:
email = partner_id and partner_id.email
is_follower = partner_id.id in [
fol.id for fol in obj.browse(cr, SUPERUSER_ID, id, context).message_follower_ids]
return { values = {
'is_user': uid != public_id, 'is_user': uid != public_id,
'email': email, 'email': email,
'is_follower': is_follower 'is_follower': False,
} }
if not obj:
return values
obj_ids = obj.exists(cr, SUPERUSER_ID, [id], context=context)
if obj_ids:
if partner_id:
values['is_follower'] = len(
request.registry['mail.followers'].search(
cr, SUPERUSER_ID, [
('res_model', '=', 'mail.group'),
('res_id', '=', obj_ids[0]),
('partner_id', '=', partner_id.id)
], context=context)) == 1
if post.get('fields'):
record = obj.read(cr, SUPERUSER_ID, obj_ids[0], fields=post.get('fields'), context=context)
values.update(record)
return values

View File

@ -34,7 +34,7 @@ class MailMessage(osv.Model):
res[message.id] = message.subject res[message.id] = message.subject
else: else:
plaintext_ct = html2plaintext(message.body) plaintext_ct = html2plaintext(message.body)
res[message.id] = plaintext_ct + '%s' % (' [...]' if len(plaintext_ct) >= 20 else '') res[message.id] = plaintext_ct[:30] + '%s' % (' [...]' if len(plaintext_ct) >= 30 else '')
return res return res
_columns = { _columns = {

View File

@ -7,17 +7,20 @@
selector: ".js_follow", selector: ".js_follow",
start: function (editable_mode) { start: function (editable_mode) {
var self = this; var self = this;
this.is_user = false;
// set value and display button
self.$target.find("input").removeClass("hidden");
openerp.jsonRpc('/website_mail/is_follower', 'call', { openerp.jsonRpc('/website_mail/is_follower', 'call', {
model: this.$target.data('object'), model: this.$target.data('object'),
id: +this.$target.data('id'), id: this.$target.data('id'),
fields: ['name', 'alias_id'],
}).always(function (data) { }).always(function (data) {
self.is_user = data.is_user;
self.$target.find('.js_mg_email').attr('href', 'mailto:' + data.alias_id[1]);
self.$target.find('.js_mg_link').attr('href', '/groups/' + data.id);
self.toggle_subscription(data.is_follower);
self.$target.find('input.js_follow_email') self.$target.find('input.js_follow_email')
.val(data.email ? data.email : "") .val(data.email ? data.email : "")
.attr("disabled", data.is_follower && data.email.length ? "disabled" : false); .attr("disabled", data.is_follower || (data.email.length && self.is_user) ? "disabled" : false);
self.$target.attr("data-follow", data.is_follower ? 'on' : 'off');
self.$target.removeClass("hidden"); self.$target.removeClass("hidden");
}); });
@ -30,10 +33,11 @@
self.on_click(); self.on_click();
}); });
} }
return;
}, },
on_click: function () { on_click: function () {
var self = this; var self = this;
var $email = this.$target.find(".js_follow_email:visible"); var $email = this.$target.find(".js_follow_email");
if ($email.length && !$email.val().match(/.+@.+/)) { if ($email.length && !$email.val().match(/.+@.+/)) {
this.$target.addClass('has-error'); this.$target.addClass('has-error');
@ -47,13 +51,27 @@
'message_is_follower': this.$target.attr("data-follow") || "off", 'message_is_follower': this.$target.attr("data-follow") || "off",
'email': $email.length ? $email.val() : false, 'email': $email.length ? $email.val() : false,
}).then(function (follow) { }).then(function (follow) {
if (follow) { self.toggle_subscription(follow);
self.$target.find(".js_follow_email, .input-group-btn").addClass("hidden");
self.$target.find(".alert").removeClass("hidden");
}
self.$target.find('input.js_follow_email').attr("disabled", follow ? "disabled" : false);
self.$target.attr("data-follow", follow ? 'on' : 'off');
}); });
}, },
toggle_subscription: function(follow) {
if (follow) {
this.$target.find(".js_mg_follow_form").addClass("hidden");
this.$target.find(".js_mg_details").removeClass("hidden");
}
else {
this.$target.find(".js_mg_follow_form").removeClass("hidden");
this.$target.find(".js_mg_details").addClass("hidden");
}
this.$target.find('input.js_follow_email').attr("disabled", follow || this.is_user ? "disabled" : false);
this.$target.attr("data-follow", follow ? 'on' : 'off');
},
});
$(document).ready(function () {
$('.js_follow_btn').on('click', function (ev) {
var email = $(ev.currentTarget).parents('.js_mg_follow_form').first().find('.js_follow_email').val();
$(document).find('.js_follow_email').val(email);
});
}); });
})(); })();

View File

@ -503,20 +503,25 @@
<span class="oe_snippet_thumbnail_title">Subscribe Button</span> <span class="oe_snippet_thumbnail_title">Subscribe Button</span>
</div> </div>
<div class="oe_snippet_body input-group js_follow" <div class="oe_snippet_body js_follow"
data-id="0" data-id="0"
data-object="mail.group" data-object="mail.group"
data-follow="off"> data-follow="off">
<input <div class="input-group js_mg_follow_form">
type="email" <input
name="email" type="email"
class="js_follow_email form-control" name="email"
placeholder="your email..."/> class="js_follow_email form-control"
<span class="input-group-btn"> placeholder="your email..."/>
<a href="#" class="btn btn-default js_unfollow_btn">Unsubscribe</a> <span class="input-group-btn">
<a href="#" class="btn btn-primary js_follow_btn">Subscribe</a> <button href="#" class="btn btn-primary js_follow_btn">Subscribe</button>
</span> </span>
<div class="alert alert-success hidden">thanks for your subscription!</div> </div>
<p class="js_mg_details hidden well well-sm">
<i class="fa fa-envelope-o"/><a href="#" class="js_mg_email"> send mail</a> -
<i class="fa fa-file-o"/><a href="#" class="js_mg_link"> archives</a> -
<i class="fa fa-times"/><a href="#" class="js_unfollow_btn"> unsubscribe</a>
</p>
</div> </div>
</div> </div>

View File

@ -9,13 +9,14 @@ from openerp.addons.web.http import request
class MailGroup(http.Controller): class MailGroup(http.Controller):
_thread_per_page = 10 _thread_per_page = 20
_replies_per_page = 10
def _get_archives(self, group_id): def _get_archives(self, group_id):
MailMessage = request.registry['mail.message'] MailMessage = request.registry['mail.message']
groups = MailMessage.read_group( groups = MailMessage.read_group(
request.cr, request.uid, [('model', '=', 'mail.group'), ('res_id', '=', group_id)], ['subject', 'date'], request.cr, request.uid, [('model', '=', 'mail.group'), ('res_id', '=', group_id)], ['subject', 'date'],
groupby="date", orderby="date asc", context=request.context) groupby="date", orderby="date desc", context=request.context)
for group in groups: for group in groups:
begin_date = datetime.datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date() begin_date = datetime.datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
end_date = datetime.datetime.strptime(group['__domain'][1][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date() end_date = datetime.datetime.strptime(group['__domain'][1][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
@ -27,7 +28,7 @@ class MailGroup(http.Controller):
def view(self, **post): def view(self, **post):
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
group_obj = request.registry.get('mail.group') group_obj = request.registry.get('mail.group')
group_ids = group_obj.search(cr, uid, [], context=context) group_ids = group_obj.search(cr, uid, [('alias_id', '!=', False), ('alias_id.alias_name', '!=', False)], context=context)
values = {'groups': group_obj.browse(cr, uid, group_ids, context)} values = {'groups': group_obj.browse(cr, uid, group_ids, context)}
return request.website.render('website_mail_group.mail_groups', values) return request.website.render('website_mail_group.mail_groups', values)
@ -73,6 +74,7 @@ class MailGroup(http.Controller):
'archives': self._get_archives(group.id), 'archives': self._get_archives(group.id),
'date_begin': date_begin, 'date_begin': date_begin,
'date_end': date_end, 'date_end': date_end,
'replies_per_page': self._replies_per_page,
} }
return request.website.render('website_mail_group.group_messages', values) return request.website.render('website_mail_group.group_messages', values)
@ -81,11 +83,49 @@ class MailGroup(http.Controller):
], type='http', auth="public", website=True) ], type='http', auth="public", website=True)
def thread_discussion(self, group, message, mode='thread', date_begin=None, date_end=None, **post): def thread_discussion(self, group, message, mode='thread', date_begin=None, date_end=None, **post):
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
Message = request.registry['mail.message']
if mode == 'thread':
base_domain = [('model', '=', 'mail.group'), ('res_id', '=', group.id), ('parent_id', '=', message.parent_id and message.parent_id.id or False)]
else:
base_domain = [('model', '=', 'mail.group'), ('res_id', '=', group.id)]
next_message = None
next_message_ids = Message.search(cr, uid, base_domain + [('date', '<', message.date)], order="date DESC", limit=1, context=context)
if next_message_ids:
next_message = Message.browse(cr, uid, next_message_ids[0], context=context)
prev_message = None
prev_message_ids = Message.search(cr, uid, base_domain + [('date', '>', message.date)], order="date ASC", limit=1, context=context)
if prev_message_ids:
prev_message = Message.browse(cr, uid, prev_message_ids[0], context=context)
values = { values = {
'message': message, 'message': message,
'group': group, 'group': group,
'mode': mode, 'mode': mode,
'archives': self._get_archives(group.id),
'date_begin': date_begin, 'date_begin': date_begin,
'date_end': date_end, 'date_end': date_end,
'replies_per_page': self._replies_per_page,
'next_message': next_message,
'prev_message': prev_message,
} }
return request.website.render('website_mail_group.group_message', values) return request.website.render('website_mail_group.group_message', values)
@http.route(
'''/groups/<model('mail.group'):group>/<model('mail.message', "[('model','=','mail.group'), ('res_id','=',group[0])]"):message>/get_replies''',
type='json', auth="public", methods=['POST'], website=True)
def render_messages(self, group, message, **post):
last_displayed_id = post.get('last_displayed_id')
if not last_displayed_id:
return False
Message = request.registry['mail.message']
replies_domain = [('id', '<', int(last_displayed_id)), ('parent_id', '=', message.id)]
msg_ids = Message.search(request.cr, request.uid, replies_domain, limit=self._replies_per_page, context=request.context)
msg_count = Message.search(request.cr, request.uid, replies_domain, count=True, context=request.context)
messages = Message.browse(request.cr, request.uid, msg_ids, context=request.context)
values = {
'group': group,
'thread_header': message,
'messages': messages,
'msg_more_count': msg_count - self._replies_per_page,
'replies_per_page': self._replies_per_page,
}
return request.registry['ir.ui.view'].render(request.cr, request.uid, 'website_mail_group.messages_short', values, engine='ir.qweb', context=request.context)

View File

@ -0,0 +1,12 @@
.o_mg_avatar {
width: 40px;
height: 40px;
}
.o_mg_link_show {
display: none;
}
.o_mg_link_content {
display: none;
}

View File

@ -0,0 +1,42 @@
$(document).ready(function () {
$('.o_mg_link_hide').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var $container = $link.parents('div').first();
$container.find('.o_mg_link_hide').first().hide();
$container.find('.o_mg_link_show').first().show();
$container.find('.o_mg_link_content').first().show();
return false;
});
$('.o_mg_link_show').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var $container = $link.parents('div').first();
$container.find('.o_mg_link_hide').first().show();
$container.find('.o_mg_link_show').first().hide();
$container.find('.o_mg_link_content').first().hide();
return false;
});
$('body').on('click', 'button.o_mg_read_more', function (ev) {
var $link = $(ev.target);
return openerp.jsonRpc($link.data('href'), 'call', {
'last_displayed_id': $link.data('msg-id'),
}).then(function (data) {
if (! data) {
return true;
}
var $thread_container = $link.parents('.o_mg_replies').first().find('ul.media-list');
if ($thread_container) {
var $last_msg = $thread_container.find('li.media').last();
$(data).find('li.media').insertAfter($last_msg);
$(data).find('p.well').appendTo($thread_container);
}
var $show_more = $link.parents('p.well').first();
$show_more.remove();
return true;
});
});
});

View File

@ -1,152 +1,240 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp> <openerp>
<data> <data>
<template id="footer_mailing_list" inherit_id="website.layout" name="Footer Mailing List Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-attf-href="/groups">Mailing List</a></li>
</xpath>
</template>
<template id="mail_groups" name="Mailing Lists">
<t t-call="website.layout">
<div class="container">
<h1>
Our Mailing Lists
</h1>
<div class="row">
<div class="col-sm-4" style="height: 140px" t-foreach="groups" t-as="group">
<img t-att-src="'/website/image?model=mail.group&amp;field=image_small&amp;id='+str(group['id'])" class="pull-left"/>
<div>
<strong><a t-attf-href="/groups/#{ slug(group) }" t-esc="group.name"/></strong>
<div t-esc="group.description" class="text-muted"/>
<t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="group_messages" name="Message Threads"> <template id="footer_mailing_list" inherit_id="website.layout" name="Footer Mailing List Link">
<t t-call="website.layout"> <xpath expr="//footer//div[@name='info']/ul" position="inside">
<section class="container"> <li><a t-attf-href="/groups">Mailing List</a></li>
<div class="row mt8"> </xpath>
<div class="col-md-5 mt16"> </template>
<ol class="breadcrumb">
<li><a href="/groups">Mailing Lists</a></li> <template id="mail_groups" name="Mailing Lists">
<li class="active" t-esc="group.name"/> <t t-call="website.layout">
</ol> <div id="wrap" class="oe_structure oe_empty">
</div> <section class="bg-primary jumbotron mt0 mb0">
<div class="col-md-5 pull-right"> <div class="container">
<t t-call="website.pager"/> <h1>Stay in touch with our Community</h1>
</div> <p>Alone we can do so little, together we can do so much</p>
</div>
<h1 class="mt8 mb32">
<span t-field="group.name"/>
<small>List Archive</small>
</h1>
<div class="row">
<div class="col-md-3">
<ul class="nav nav-pills nav-stacked" id="group_mode">
<li t-attf-class="#{mode=='thread' and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=thread">By thread</a>
</li>
<li t-attf-class="#{mode=='date' and not date_begin and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=date">By date</a>
<ul class="nav nav-pills nav-stacked" style="margin-left: 8px;">
<t t-foreach="archives" t-as="month_archive">
<li t-att-class="month_archive['date_begin'] == date_begin and 'active' or None">
<a t-ignore="True" t-attf-href="/groups/#{ slug(group) }?mode=date&amp;date_begin=#{ month_archive['date_begin'] }&amp;date_end=#{month_archive['date_end']}">
<t t-esc="month_archive['date']"/>
<span class="pull-right badge" t-esc="month_archive['date_count']"/>
</a>
</li>
</t>
</ul>
</li>
</ul>
</div>
<div class="col-md-9">
<t t-call="website_mail_group.messages_short"/>
</div>
</div> </div>
</section> </section>
</t> </div>
</template> <div class="container mt32">
<div class="row mt8" t-foreach="groups" t-as="group">
<template id="group_message"> <div class="col-md-3">
<t t-call="website.layout"> <img t-att-src="'/website/image?model=mail.group&amp;field=image_small&amp;id='+str(group['id'])" class="pull-left"/>
<div class="container"> <strong><a t-attf-href="/groups/#{ slug(group) }" t-esc="group.name"/></strong><br />
<div class="row mt8"> <i class='fa fa-envelope-o'/>
<div class="col-md-5"> <a t-attf-href="mailto:#{group.alias_id.alias_name}@#{group.alias_id.alias_domain}"><span t-field="group.alias_id"/></a>
<ol class="breadcrumb mb8">
<li><a href="/groups">Mailing Lists</a></li>
<li>
<a t-attf-href="/groups/#{slug(group)}?mode=#{mode}&amp;date_begin=#{date_begin}&amp;date_end=#{date_end}">
<span t-field="group.name"/>
</a>
</li>
<li class="active" t-esc="message.subject or 'Message'"/>
</ol>
</div>
</div> </div>
<div class="col-md-4">
<h1 t-field="message.subject"/> <div t-esc="group.description" class="text-muted"/>
<img class="img-rounded pull-left" t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(message.id)" style="width : 30px"/>
<h4 class="mt0 mb32">
<t t-if="message.author_id">
<span t-field="message.author_id" style="display: inline-block;" t-field-options='{
"widget": "contact",
"fields": ["name"]
}'/>
</t>
<t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
on <span t-field="message.date"/>
</h4>
<div t-raw="message.body"/>
<div class="row" t-if="message.attachment_ids">
<h3 class="col-sm-12">Attachment(s):</h3>
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-att-href="'/mail/download_attachment?model=mail.message&amp;id='+str(message.id)+'&amp;method=download_attachment&amp;attachment_id='+str(attachment.id)" target="_blank">
<t t-if="attachment.file_type == 'webimage'">
<img t-att-src="'/web/binary/image?model=ir.attachment&amp;field=datas&amp;id=' + str(attachment.id) + '&amp;resize=100,80'"
class='oe_attachment_embedded'></img>
</t>
<t t-if="attachment.file_type != 'webimage'">
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type + '.png'"
class='oe_attachment_webimage'></img>
</t>
<div class='oe_attachment_name'><t t-raw='attachment.name' /></div>
</a>
</div>
</div> </div>
<div t-if="message.child_ids"> <div class="col-md-2">
<h2 class="page-header">Follow ups</h2> <i class='fa fa-user'/> <t t-esc="len(group.message_follower_ids)"/> participants<br />
<t t-set="messages" t-value="message.child_ids"/> <i class='fa fa-envelope-o'/> <t t-esc="len(group.message_ids)"/> messages
<t t-call="website_mail_group.messages_short"/>
</div> </div>
<div t-if="message.parent_id"> <div class="col-md-3">
<h2 class="page-header">Reference</h2>
<t t-set="messages" t-value="[message.parent_id]"/>
<t t-call="website_mail_group.messages_short"/>
</div>
<div class="jumbotron mt64">
<h1>Join the discussion</h1>
<p>
Join this mailing list to follow or participate to this discussion.<br/>
<span t-field="group.name"/>: <i t-field="group.description"/>
</p>
<t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t> <t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
</div> </div>
</div> </div>
</t> </div>
</template> </t>
</template>
<template id="messages_short"> <template id="group_messages" name="Message Threads">
<t t-call="website.layout">
<t t-set="head">
<link rel='stylesheet' href="/website_mail_group/static/src/css/website_mail_group.css"/>
<script type="text/javascript" src="/website_mail_group/static/src/js/website_mail_group.js"/>
</t>
<section class="container">
<div class="row mt8">
<ol class="breadcrumb pull-left">
<li><a href="/groups">Mailing Lists</a></li>
<li>
<a t-attf-href="/groups/#{slug(group)}?#{mode and 'mode=%s' % mode or ''}#{date_begin and '&amp;date_begin=%s' % date_begin or ''}#{date_end and '&amp;date_end=%s' % date_end or ''}"><t t-esc="group.name"/></a>
</li>
</ol>
</div>
<div class="row">
<h1 class="text-center">
<t t-esc="group.name"/> mailing list archives
</h1><h4 class="text-center text-muted">
<i class='fa fa-envelope-o'/>
<a t-attf-href="mailto:#{group.alias_id.alias_name}@#{group.alias_id.alias_domain}"><span t-field="group.alias_id"/></a>
</h4>
</div>
<div class="row">
<div class="col-md-3">
<h2>Archives</h2>
<ul class="nav nav-pills nav-stacked" id="group_mode">
<li t-attf-class="#{mode=='thread' and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=thread">By thread</a>
</li>
<li t-attf-class="#{mode=='date' and not date_begin and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=date">By date</a>
<ul class="nav nav-pills nav-stacked" style="margin-left: 8px;">
<t t-foreach="archives" t-as="month_archive">
<li t-att-class="month_archive['date_begin'] == date_begin and 'active' or None">
<a t-ignore="True" t-attf-href="/groups/#{ slug(group) }?mode=date&amp;date_begin=#{ month_archive['date_begin'] }&amp;date_end=#{month_archive['date_end']}">
<t t-esc="month_archive['date']"/>
<span class="pull-right badge" t-esc="month_archive['date_count']"/>
</a>
</li>
</t>
</ul>
</li>
</ul>
</div>
<div class="col-md-9">
<div>
<t t-call="website.pager"/>
</div>
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="messages"/>
<t t-set="msg_more_count" t-value="0"/>
<t t-set="thread_header" t-value="None"/>
</t>
<div>
<t t-call="website.pager"/>
</div>
</div>
</div>
</section>
</t>
</template>
<template id="group_message">
<t t-call="website.layout">
<t t-set="head">
<link rel='stylesheet' href="/website_mail_group/static/src/css/website_mail_group.css"/>
<script type="text/javascript" src="/website_mail_group/static/src/js/website_mail_group.js"/>
</t>
<section class="container">
<div class="row mt8">
<ol class="breadcrumb pull-left">
<li><a href="/groups">Mailing Lists</a></li>
<li>
<a t-attf-href="/groups/#{slug(group)}?#{mode and 'mode=%s' % mode or ''}#{date_begin and '&amp;date_begin=%s' % date_begin or ''}#{date_end and '&amp;date_end=%s' % date_end or ''}"><t t-esc="group.name"/></a>
</li>
<li t-if="message" class="active"><t t-esc="message.description"/></li>
</ol>
</div>
<div class="row">
<h1 class="text-center">
<t t-esc="group.name"/> mailing list archives
</h1><h4 class="text-center text-muted">
<i class='fa fa-envelope-o'/>
<a t-attf-href="mailto:#{group.alias_id.alias_name}@#{group.alias_id.alias_domain}"><span t-field="group.alias_id"/></a>
</h4>
</div>
<div class="row">
<div class="col-md-3">
<h4>Browse archives</h4>
<ul class="nav nav-pills nav-stacked" id="group_mode">
<li t-attf-class="#{mode=='thread' and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=thread">By thread</a>
</li>
<li t-attf-class="#{mode=='date' and not date_begin and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=date">By date</a>
<ul class="nav nav-pills nav-stacked" style="margin-left: 8px;">
<t t-foreach="archives" t-as="month_archive">
<li t-att-class="month_archive['date_begin'] == date_begin and 'active' or None">
<a t-ignore="True" t-attf-href="/groups/#{ slug(group) }?mode=date&amp;date_begin=#{ month_archive['date_begin'] }&amp;date_end=#{month_archive['date_end']}">
<t t-esc="month_archive['date']"/>
<span class="pull-right badge" t-esc="month_archive['date_count']"/>
</a>
</li>
</t>
</ul>
</li>
</ul>
</div>
<div class="col-md-9">
<div class="row">
<h4 class="col-md-6">
<t t-if="prev_message"><a t-attf-href='/groups/#{slug(group)}/#{slug(prev_message)}?#{mode and "mode=%s" % mode or ""}'>
<i class="fa fa-arrow-left"/> <t t-esc="prev_message.description"/>
</a></t>
</h4>
<h4 class="col-md-6">
<t t-if="next_message"><a class="pull-right" t-attf-href='/groups/#{slug(group)}/#{slug(next_message)}?#{mode and "mode=%s" % mode or ""}'>
<t t-esc="next_message.description"/> <i class="fa fa-arrow-right"/>
</a></t>
</h4>
</div>
<div class="media">
<img class="img-rounded pull-left mt0 media-object o_mg_avatar"
t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(message.id)"/>
<div class="media-body">
<h4 class="media-heading" t-esc="message.description"/>
<small>
by
<t t-if="message.author_id">
<span t-field="message.author_id" style="display: inline-block;" t-field-options='{
"widget": "contact",
"fields": ["name"]
}'/>
</t>
<t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
- <i class="fa fa-calendar"/> <t t-esc="message.date"/>
</small>
<div t-raw="message.body"/>
<div>
<p t-if="message.attachment_ids" class="mt8">
<a href="#" class="o_mg_link_hide">
<i class="fa fa-chevron-right"/> <t t-raw="len(message.attachment_ids)"/> attachments
</a>
<a href="#" class="o_mg_link_show">
<i class="fa fa-chevron-down"/> <t t-raw="len(message.attachment_ids)"/> attachments
</a>
</p>
<div class="o_mg_link_content">
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-att-href="'/mail/download_attachment?model=mail.message&amp;id='+str(message.id)+'&amp;method=download_attachment&amp;attachment_id='+str(attachment.id)" target="_blank">
<t t-if="attachment.file_type == 'webimage'">
<img t-att-src="'/web/binary/image?model=ir.attachment&amp;field=datas&amp;id=' + str(attachment.id) + '&amp;resize=100,80'"
class='oe_attachment_embedded'
t-att-title="attachment.name"/>
</t>
<t t-if="attachment.file_type != 'webimage'">
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type + '.png'"
class='oe_attachment_webimage'
t-att-title="attachment.name"/>
</t>
<div class='oe_attachment_name'><t t-raw='attachment.name' /></div>
</a>
</div>
</div>
</div>
</div>
</div>
<div t-if="message.child_ids" class="o_mg_replies">
<h4 class="page-header">Follow-Ups</h4>
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="message.child_ids[:replies_per_page]"/>
<t t-set="msg_more_count" t-value="len(message.child_ids) - replies_per_page"/>
<t t-set="thread_header" t-value="message"/>
</t>
</div>
<div t-if="message.parent_id">
<h4 class="page-header">Reference</h4>
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="[message.parent_id]"/>
</t>
</div>
</div>
</div>
</section>
</t>
</template>
<template id="messages_short">
<div>
<ul class="media-list"> <ul class="media-list">
<li t-foreach="messages" t-as="thread" class="media"> <li t-foreach="messages" t-as="thread" class="media">
<img class="img-rounded pull-left mt0 media-object" style="height: 40px" <img class="img-rounded pull-left mt0 media-object o_mg_avatar"
t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(thread.id)"/> t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(thread.id)"/>
<div class="media-body"> <div class="media-body">
<h4 class="media-heading"> <h4 class="media-heading">
@ -161,14 +249,36 @@
}'/> }'/>
</t> </t>
<t t-if="not thread.author_id"><t t-esc="thread.email_from"/></t> <t t-if="not thread.author_id"><t t-esc="thread.email_from"/></t>
<span class="fa fa-comment-o"> - <i class="fa fa-calendar"/> <t t-esc="thread.date"/>
<t t-raw="len(thread.child_ids)"/> replies - <i class="fa fa-paperclip"/> <t t-esc="len(thread.attachment_ids)"/>
</span>
</small> </small>
<p t-if="thread.child_ids" class="mt8">
<a href="#" class="o_mg_link_hide">
<i class="fa fa-chevron-right"/> <t t-raw="len(thread.child_ids)"/> replies
</a>
<a href="#" class="o_mg_link_show">
<i class="fa fa-chevron-down"/> <t t-raw="len(thread.child_ids)"/> replies
</a>
</p>
<div class="o_mg_link_content o_mg_replies">
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="thread.child_ids[:replies_per_page]"/>
<t t-set="msg_more_count" t-value="len(thread.child_ids) - replies_per_page"/>
<t t-set="thread_header" t-value="thread"/>
</t>
</div>
</div> </div>
</li> </li>
</ul> </ul>
</template> <p t-if="messages and msg_more_count > 0 and thread_header" class="well well-sm">
<button class="fa btn-link o_mg_read_more"
t-attf-data-href="/groups/#{slug(group)}/#{slug(thread_header)}/get_replies"
t-attf-data-msg-id="#{messages[-1].id}">
show <t t-esc="msg_more_count"/> more replies
</button>
</p>
</div>
</template>
</data> </data>
</openerp> </openerp>

View File

@ -84,7 +84,7 @@
</template> </template>
<template id="opt_index_country" name="Location" <template id="opt_index_country" name="Location"
inherit_option_id="website_membership.index" inherit_id="website_membership.index"> optional="enabled" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[last()]" position="after"> <xpath expr="//div[@id='left_column']/ul[last()]" position="after">
<ul class="nav nav-pills nav-stacked mt16"> <ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>Location</h3></li> <li class="nav-header"><h3>Location</h3></li>
@ -101,7 +101,7 @@
<!-- Option: index: Left Google Map --> <!-- Option: index: Left Google Map -->
<template id="opt_index_google_map" name="Left World Map" <template id="opt_index_google_map" name="Left World Map"
inherit_option_id="website_membership.index" inherit_id="website_membership.index"> optional="enabled" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[1]" position="before"> <xpath expr="//div[@id='left_column']/ul[1]" position="before">
<ul class="nav nav-pills nav-stacked mt16"> <ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>World Map</h3></li> <li class="nav-header"><h3>World Map</h3></li>

View File

@ -92,7 +92,7 @@
</section> </section>
</template> </template>
<template id="change_quantity" inherit_option_id="website_quote.pricing" name="Change Quantity"> <template id="change_quantity" inherit_id="website_quote.pricing" optional="disabled" name="Change Quantity">
<xpath expr="//div[@id='quote_qty']" position="replace"> <xpath expr="//div[@id='quote_qty']" position="replace">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon hidden-print"> <span class="input-group-addon hidden-print">
@ -130,7 +130,7 @@
</template> </template>
<!-- Options:Quotation Chatter: user can reply --> <!-- Options:Quotation Chatter: user can reply -->
<template id="opt_quotation_chatter_post_complete_comment" name="Allow Comments" inherit_option_id="website_quote.chatter" inherit_id="website_quote.chatter"> <template id="opt_quotation_chatter_post_complete_comment" name="Allow Comments" optional="enabled" inherit_id="website_quote.chatter">
<xpath expr="//h1" position="after"> <xpath expr="//h1" position="after">
<section class="mb32 css_editable_mode_hidden hidden-print"> <section class="mb32 css_editable_mode_hidden hidden-print">
<form id="comment" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/post" method="POST"> <form id="comment" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/post" method="POST">
@ -388,7 +388,7 @@
</template> </template>
<!-- Options:Quotation Signature --> <!-- Options:Quotation Signature -->
<template id="opt_quotation_signature" name="Ask Signature" inherit_option_id="website_quote.so_quotation" inherit_id="website_quote.so_quotation"> <template id="opt_quotation_signature" name="Ask Signature" optional="enabled" inherit_id="website_quote.so_quotation">
<xpath expr="//div[@id='sign-dialog']" position="inside"> <xpath expr="//div[@id='sign-dialog']" position="inside">
<div class="panel panel-default mt16 mb0" id="drawsign"> <div class="panel panel-default mt16 mb0" id="drawsign">
<div class="panel-heading"> <div class="panel-heading">

View File

@ -35,8 +35,8 @@ class table_compute(object):
index = 0 index = 0
maxy = 0 maxy = 0
for p in products: for p in products:
x = p.website_size_x x = min(max(p.website_size_x, 1), PPR)
y = p.website_size_y y = min(max(p.website_size_y, 1), PPR)
if index>PPG: if index>PPG:
x = y = 1 x = y = 1
@ -490,7 +490,6 @@ class website_sale(http.Controller):
values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context) values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context)
render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now') render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now')
for acquirer in values['acquirers']: for acquirer in values['acquirers']:
render_ctx['tx_url'] = '/shop/payment/transaction/%s' % acquirer.id
acquirer.button = payment_obj.render( acquirer.button = payment_obj.render(
cr, SUPERUSER_ID, acquirer.id, cr, SUPERUSER_ID, acquirer.id,
order.name, order.name,
@ -504,20 +503,17 @@ class website_sale(http.Controller):
return request.website.render("website_sale.payment", values) return request.website.render("website_sale.payment", values)
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='http', methods=['POST'], auth="public", website=True) @http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
def payment_transaction(self, acquirer_id, **post): def payment_transaction(self, acquirer_id):
""" Hook method that creates a payment.transaction and redirect to the """ Json method that creates a payment.transaction, used to create a
acquirer, using post values to re-create the post action. transaction when the user clicks on 'pay now' button. After having
created the transaction, the event continues and the user is redirected
to the acquirer website.
:param int acquirer_id: id of a payment.acquirer record. If not set the :param int acquirer_id: id of a payment.acquirer record. If not set the
user is redirected to the checkout page user is redirected to the checkout page
:param dict post: should coutain all post data for the acquirer
""" """
# @TDEFIXME: don't know why we received those data, but should not be send to the acquirer
post.pop('submit.x', None)
post.pop('submit.y', None)
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
payment_obj = request.registry.get('payment.acquirer')
transaction_obj = request.registry.get('payment.transaction') transaction_obj = request.registry.get('payment.transaction')
sale_order_obj = request.registry['sale.order'] sale_order_obj = request.registry['sale.order']
order = request.website.sale_get_order(context=context) order = request.website.sale_get_order(context=context)
@ -529,7 +525,13 @@ class website_sale(http.Controller):
# find an already existing transaction # find an already existing transaction
tx = request.website.sale_get_transaction() tx = request.website.sale_get_transaction()
if not tx: if tx:
if tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx.write({
'acquirer_id': acquirer_id,
})
tx_id = tx.id
else:
tx_id = transaction_obj.create(cr, SUPERUSER_ID, { tx_id = transaction_obj.create(cr, SUPERUSER_ID, {
'acquirer_id': acquirer_id, 'acquirer_id': acquirer_id,
'type': 'form', 'type': 'form',
@ -541,10 +543,6 @@ class website_sale(http.Controller):
'sale_order_id': order.id, 'sale_order_id': order.id,
}, context=context) }, context=context)
request.session['sale_transaction_id'] = tx_id request.session['sale_transaction_id'] = tx_id
elif tx and tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx.write({
'acquirer_id': acquirer_id,
})
# update quotation # update quotation
sale_order_obj.write( sale_order_obj.write(
@ -555,9 +553,7 @@ class website_sale(http.Controller):
# confirm the quotation # confirm the quotation
sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context) sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
acquirer_form_post_url = payment_obj.get_form_action_url(cr, uid, acquirer_id, context=context) return tx_id
acquirer_total_url = '%s?%s' % (acquirer_form_post_url, werkzeug.url_encode(post))
return request.redirect(acquirer_total_url)
@http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True) @http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
def payment_get_status(self, sale_order_id, **post): def payment_get_status(self, sale_order_id, **post):

View File

@ -65,6 +65,7 @@ class product_template(osv.Model):
_inherit = ["product.template", "website.seo.metadata"] _inherit = ["product.template", "website.seo.metadata"]
_order = 'website_published desc, website_sequence desc, name' _order = 'website_published desc, website_sequence desc, name'
_name = 'product.template' _name = 'product.template'
_mail_post_access = 'read'
def _website_url(self, cr, uid, ids, field_name, arg, context=None): def _website_url(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, '') res = dict.fromkeys(ids, '')

View File

@ -78,7 +78,7 @@
{ {
title: "Pay Now", title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])', waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible', element: '.oe_sale_acquirer_button .btn[type="submit"]:visible',
}, },
{ {
title: "finish", title: "finish",

Some files were not shown because too many files have changed in this diff Show More