Merge remote-tracking branch 'odoo/master' into master-inline-searchview-ged
This commit is contained in:
commit
5cc8f9ca42
|
@ -25,4 +25,4 @@ install/win32/meta.py
|
|||
/lib/
|
||||
/man/
|
||||
/share/
|
||||
/src/
|
||||
/src/
|
|
@ -60,7 +60,7 @@
|
|||
</group>
|
||||
<group>
|
||||
<field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '<>', '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','<>','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"/>
|
||||
</group>
|
||||
|
|
|
@ -956,7 +956,6 @@
|
|||
<field name="ref_tax_sign"/>
|
||||
</group>
|
||||
<group string="Children/Sub Taxes" colspan="2">
|
||||
<field name="child_depend" class="oe_inline"/>
|
||||
<field name="child_ids" nolabel="1" colspan="2">
|
||||
<tree string="Account Tax">
|
||||
<field name="sequence"/>
|
||||
|
|
|
@ -161,7 +161,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
|
|||
dates_query += ' < %s)'
|
||||
args_list += (form[str(i)]['stop'],)
|
||||
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
|
||||
WHERE (l.account_id = account_account.id) AND (l.move_id=am.id)
|
||||
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 ''' + dates_query + '''
|
||||
AND (l.date <= %s)
|
||||
GROUP BY l.partner_id''', args_list)
|
||||
t = self.cr.fetchall()
|
||||
d = {}
|
||||
for i in t:
|
||||
d[i[0]] = i[1]
|
||||
history.append(d)
|
||||
GROUP BY l.partner_id, l.reconcile_partial_id''', args_list)
|
||||
partners_partial = self.cr.fetchall()
|
||||
partners_amount = dict((i[0],0) for i in partners_partial)
|
||||
for partner_info in partners_partial:
|
||||
if partner_info[2]:
|
||||
# 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:
|
||||
values = {}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
|
||||
<span t-field="o.partner_id.vat"/>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<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-raw="addresses[o.id].replace('\n\n', '\n').replace('\n', '<br>')"/>
|
||||
<span t-field="o.vat"/>
|
||||
|
|
|
@ -69,7 +69,7 @@ class account_analytic_invoice_line(osv.osv):
|
|||
if partner_id:
|
||||
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=local_context)
|
||||
if part.lang:
|
||||
context.update({'lang': part.lang})
|
||||
local_context.update({'lang': part.lang})
|
||||
|
||||
result = {}
|
||||
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
|
||||
else:
|
||||
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}
|
||||
if result['uom_id'] != res.uom_id.id:
|
||||
|
|
|
@ -169,7 +169,7 @@
|
|||
<div attrs="{'invisible': [('recurring_invoices','=',False)]}">
|
||||
<field name="recurring_invoice_line_ids">
|
||||
<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="quantity"/>
|
||||
<field name="uom_id"/>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
import datetime
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import ustr
|
||||
from openerp.tools.translate import _
|
||||
|
||||
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):
|
||||
acc_ids = [x.id for x in line.general_budget_id.account_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_from = line.date_from
|
||||
if context.has_key('wizard_date_from'):
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
<span t-field="o.partner_id.vat"/>
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -13,7 +13,7 @@ for customization purpose.
|
|||
'depends': ['web'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'data': [],
|
||||
'data': ['views/base_import_module.xml'],
|
||||
'qweb': [],
|
||||
'test': [],
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import functools
|
||||
import os
|
||||
import zipfile
|
||||
from os.path import join as opj
|
||||
|
||||
import openerp
|
||||
from openerp.http import Controller, route, request, Response
|
||||
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
|
||||
|
||||
def webservice(f):
|
||||
@functools.wraps(f)
|
||||
def wrap(*args, **kw):
|
||||
|
@ -42,32 +36,4 @@ class ImportModule(Controller):
|
|||
@webservice
|
||||
def upload(self, mod_file=None, **kw):
|
||||
self.check_user()
|
||||
imm = request.registry['ir.module.module']
|
||||
|
||||
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)
|
||||
return request.registry['ir.module.module'].import_zipfile(request.cr, request.uid, mod_file, context=request.context)[0]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import ir_module
|
||||
import base_import_module
|
||||
|
|
|
@ -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:
|
|
@ -1,14 +1,18 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
from os.path import join as opj
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv
|
||||
from openerp.tools import convert_file
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes
|
||||
|
||||
class view(osv.osv):
|
||||
_inherit = "ir.module.module"
|
||||
|
||||
|
@ -22,7 +26,7 @@ class view(osv.osv):
|
|||
|
||||
unmet_dependencies = set(terp['depends']).difference(known_mods_names.keys())
|
||||
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:
|
||||
self.write(cr, uid, mod.id, values)
|
||||
|
@ -69,3 +73,33 @@ class view(osv.osv):
|
|||
|
||||
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
|
||||
|
|
|
@ -3,49 +3,51 @@
|
|||
<data>
|
||||
|
||||
<record id="view_base_module_import" model="ir.ui.view">
|
||||
<field name="name">Module Import</field>
|
||||
<field name="model">base.module.import</field>
|
||||
<field name="name">Import Module</field>
|
||||
<field name="model">base.import.module</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import module" version="7.0">
|
||||
<field name="state" invisible="1"/>
|
||||
<separator string="Module Import" colspan="4"/>
|
||||
<separator string="Import Module" colspan="4"/>
|
||||
<group states="init" col="4">
|
||||
<label string="Select module package to import (.zip file):" colspan="4"/>
|
||||
<field name="module_file" colspan="4"/>
|
||||
</group>
|
||||
<group states="done" col="4">
|
||||
<label string="Module file successfully imported!" colspan="4"/>
|
||||
<field name="import_message" colspan="4" nolabel="1" readonly="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="importzip" string="Import module" type="object" states="init" class="oe_highlight"/>
|
||||
<label string="or" states="init"/>
|
||||
<button name="action_module_open" string="Open Modules" type="object" states="done" class="oe_highlight"/>
|
||||
<label string="or" states="done"/>
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
<div states="init">
|
||||
<button name="import_module" string="Import Module" type="object" class="oe_highlight"/> or
|
||||
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||
</div>
|
||||
<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>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<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="res_model">base.module.import</field>
|
||||
<field name="res_model">base.import.module</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- This feature is now deprecated, but may come back later.
|
||||
<menuitem
|
||||
name="Import Module"
|
||||
action="action_view_base_module_import"
|
||||
id="menu_view_base_module_import"
|
||||
parent="menu_management"
|
||||
parent="base.menu_management"
|
||||
groups="base.group_no_one"
|
||||
sequence="6"/>
|
||||
-->
|
||||
sequence="100"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
|
|
@ -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_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_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_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
|
||||
|
|
|
|
@ -192,15 +192,16 @@ class delivery_grid(osv.osv):
|
|||
weight = 0
|
||||
volume = 0
|
||||
quantity = 0
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
for line in order.order_line:
|
||||
if not line.product_id or line.is_delivery:
|
||||
continue
|
||||
weight += (line.product_id.weight or 0.0) * line.product_uom_qty
|
||||
volume += (line.product_id.volume or 0.0) * line.product_uom_qty
|
||||
quantity += 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)
|
||||
weight += (line.product_id.weight or 0.0) * q
|
||||
volume += (line.product_id.volume or 0.0) * q
|
||||
quantity += q
|
||||
total = order.amount_total or 0.0
|
||||
|
||||
|
||||
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):
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<tr>
|
||||
<td><strong>Address</strong></td>
|
||||
<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}'/>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -114,6 +114,15 @@ class hr_applicant(osv.Model):
|
|||
return int(department_ids[0][0])
|
||||
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):
|
||||
access_rights_uid = access_rights_uid or uid
|
||||
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,
|
||||
'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),
|
||||
'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,
|
||||
'date_last_stage_update': fields.datetime.now,
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<div class="oe_structure"/>
|
||||
|
||||
<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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
|
||||
</div>
|
||||
|
|
|
@ -669,9 +669,10 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
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:
|
||||
sub_ctx = dict(context)
|
||||
sub_ctx.pop('thread_model')
|
||||
return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
|
||||
if hasattr(self.pool[context['thread_model']], 'message_get_default_recipients'):
|
||||
sub_ctx = dict(context)
|
||||
sub_ctx.pop('thread_model')
|
||||
return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
|
||||
res = {}
|
||||
for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||
recipient_ids, email_to, email_cc = set(), False, False
|
||||
|
|
|
@ -919,15 +919,45 @@ openerp.mail = function (session) {
|
|||
this.$('.oe_mail_expand').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_author').on('click', this.on_record_author_clicked);
|
||||
},
|
||||
|
||||
on_record_clicked: function (event) {
|
||||
event.preventDefault();
|
||||
var self = this;
|
||||
var state = {
|
||||
'model': this.model,
|
||||
'id': this.res_id,
|
||||
'title': this.record_name
|
||||
};
|
||||
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. */
|
||||
|
|
|
@ -276,7 +276,7 @@
|
|||
<t t-if="widget.attachment_ids.length > 0">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
</t>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&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&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>
|
||||
<t t-if="widget.type == 'notification'">
|
||||
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-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
|
||||
<span t-attf-class="oe_partner_follower">
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-esc="partner[1]"/></a>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&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]"/>
|
||||
</span>
|
||||
<t t-if="!partner_last">,</t>
|
||||
|
|
|
@ -4,10 +4,9 @@ from datetime import datetime
|
|||
from dateutil import relativedelta
|
||||
import json
|
||||
import random
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
from openerp import tools
|
||||
from openerp.exceptions import Warning
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.translate import _
|
||||
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)
|
||||
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):
|
||||
"""Model of a contact list. """
|
||||
|
@ -549,6 +554,8 @@ class MassMailing(osv.Model):
|
|||
for mailing in self.browse(cr, uid, ids, context=context):
|
||||
# instantiate an email composer + send emails
|
||||
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)
|
||||
composer_values = {
|
||||
'author_id': author_id,
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
|
||||
</div>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
|
@ -16,4 +16,7 @@ class AccountPaymentConfig(osv.TransientModel):
|
|||
'module_payment_adyen': fields.boolean(
|
||||
'Manage Payments Using Adyen',
|
||||
help='-It installs the module payment_adyen.'),
|
||||
'module_payment_buckaroo': fields.boolean(
|
||||
'Manage Payments Using Buckaroo',
|
||||
help='-It installs the module payment_buckaroo.'),
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
<field name="module_payment_adyen" class="oe_inline"/>
|
||||
<label for="module_payment_adyen"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="module_payment_buckaroo" class="oe_inline"/>
|
||||
<label for="module_payment_buckaroo"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<input t-if="tx_values.get('merchantReturnData')" type='hidden' name='merchantReturnData'
|
||||
t-att-value="tx_values.get('merchantReturnData')"/>
|
||||
<!-- submit -->
|
||||
<button type="image" name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<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>
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import main
|
|
@ -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)
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import buckaroo
|
|
@ -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 |
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.addons.payment_buckaroo.tests import test_buckaroo
|
||||
|
||||
checks = [
|
||||
test_buckaroo,
|
||||
]
|
|
@ -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')
|
|
@ -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>
|
|
@ -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>
|
|
@ -41,7 +41,7 @@
|
|||
<input type='hidden' name='EXCEPTIONURL' t-att-value='tx_values["EXCEPTIONURL"]'/>
|
||||
<input type='hidden' name='CANCELURL' t-att-value='tx_values["CANCELURL"]'/>
|
||||
<!-- submit -->
|
||||
<button type="image" name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<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>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<input t-if="tx_values.get('cancel_return')" type="hidden" name="cancel_return"
|
||||
t-att-value="tx_values.get('cancel_return')"/>
|
||||
<!-- submit -->
|
||||
<button type="image" name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<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>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<input type='hidden' name='amount' t-att-value='amount or "0.0"'/>
|
||||
<input type='hidden' name='currency' t-att-value='currency.name'/>
|
||||
<!-- submit -->
|
||||
<button name="submit" width="100px"
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<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>
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
<p style="border-left: 1px solid #8e0000; margin-left: 30px;">
|
||||
<strong>REFERENCES</strong><br />
|
||||
Order number: <strong>${object.name}</strong><br />
|
||||
Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br />
|
||||
Order total: <strong>${object.amount_total} ${object.currency_id.name}</strong><br />
|
||||
Order date: ${object.date_order}<br />
|
||||
% if object.origin:
|
||||
Order reference: ${object.origin}<br />
|
||||
|
|
|
@ -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."),
|
||||
'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'),
|
||||
'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'),
|
||||
'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)]},
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
|
|
|
@ -66,7 +66,7 @@ class ReportController(Controller):
|
|||
# Misc. route utils
|
||||
#------------------------------------------------------
|
||||
@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.
|
||||
Samples:
|
||||
<img t-att-src="'/report/barcode/QR/%s' % o.name"/>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<field name="page_width">0</field>
|
||||
<field name="orientation">Portrait</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_right">7</field>
|
||||
<field name="header_line" eval="False" />
|
||||
|
|
|
@ -25,10 +25,8 @@ from openerp.tools.translate import _
|
|||
from openerp.addons.web.http import request
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import psutil
|
||||
import signal
|
||||
import base64
|
||||
import logging
|
||||
import tempfile
|
||||
|
@ -52,15 +50,19 @@ try:
|
|||
['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
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:
|
||||
out, err = process.communicate()
|
||||
version = out.splitlines()[1].strip()
|
||||
version = version.split(' ')[1]
|
||||
version = re.search('([0-9.]+)', out).group(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 = '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):
|
||||
|
@ -343,19 +345,20 @@ class Report(osv.Model):
|
|||
command_args = []
|
||||
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:
|
||||
if request:
|
||||
command_args.extend(['--cookie', 'session_id', request.session.sid])
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Display arguments
|
||||
# Wkhtmltopdf arguments
|
||||
command_args.extend(['--quiet']) # Less verbose error messages
|
||||
if paperformat:
|
||||
# Convert the paperformat record into arguments
|
||||
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:
|
||||
command_args_copy = list(command_args)
|
||||
for index, elem in enumerate(command_args_copy):
|
||||
|
@ -366,61 +369,46 @@ class Report(osv.Model):
|
|||
elif landscape and not '--orientation' in command_args:
|
||||
command_args.extend(['--orientation', 'landscape'])
|
||||
|
||||
# Execute WKhtmltopdf
|
||||
pdfdocuments = []
|
||||
# HTML to PDF thanks to WKhtmltopdf
|
||||
for index, reporthtml in enumerate(bodies):
|
||||
command_arg_local = []
|
||||
pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.',
|
||||
mode='w+b')
|
||||
# Directly load the document if we have it
|
||||
local_command_args = []
|
||||
pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', mode='w+b')
|
||||
|
||||
# Directly load the document if we already have it
|
||||
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
|
||||
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
|
||||
pdfreport.seek(0)
|
||||
pdfdocuments.append(pdfreport)
|
||||
continue
|
||||
|
||||
# Header stuff
|
||||
# Wkhtmltopdf handles header/footer as separate pages. Create them if necessary.
|
||||
if headers:
|
||||
head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+')
|
||||
head_file.write(headers[index])
|
||||
head_file.seek(0)
|
||||
command_arg_local.extend(['--header-html', head_file.name])
|
||||
|
||||
# Footer stuff
|
||||
local_command_args.extend(['--header-html', head_file.name])
|
||||
if footers:
|
||||
foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+')
|
||||
foot_file.write(footers[index])
|
||||
foot_file.seek(0)
|
||||
command_arg_local.extend(['--footer-html', foot_file.name])
|
||||
local_command_args.extend(['--footer-html', foot_file.name])
|
||||
|
||||
# Body stuff
|
||||
content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+')
|
||||
content_file.write(reporthtml[1])
|
||||
content_file.seek(0)
|
||||
|
||||
try:
|
||||
# If the server is running with only one worker, ask to create a secund to be able
|
||||
# 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 = command + command_args + local_command_args
|
||||
wkhtmltopdf += [content_file.name] + [pdfreport.name]
|
||||
|
||||
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = process.communicate()
|
||||
|
||||
if config['workers'] == 1:
|
||||
os.kill(ppid, signal.SIGTTOU)
|
||||
|
||||
if process.returncode != 0:
|
||||
if process.returncode not in [0, 1]:
|
||||
raise osv.except_osv(_('Report (PDF)'),
|
||||
_('wkhtmltopdf failed with error code = %s. '
|
||||
_('Wkhtmltopdf failed (error code: %s). '
|
||||
'Message: %s') % (str(process.returncode), err))
|
||||
|
||||
# Save the pdf in attachment if marked
|
||||
|
@ -446,7 +434,7 @@ class Report(osv.Model):
|
|||
except:
|
||||
raise
|
||||
|
||||
# Get and return the full pdf
|
||||
# Return the entire document
|
||||
if len(pdfdocuments) == 1:
|
||||
content = pdfdocuments[0].read()
|
||||
pdfdocuments[0].close()
|
||||
|
|
|
@ -54,33 +54,35 @@ openerp.report = function(instance) {
|
|||
if (action.report_type == 'qweb-html') {
|
||||
window.open(report_url, '_blank', 'height=900,width=1280');
|
||||
instance.web.unblockUI();
|
||||
} else {
|
||||
} else if (action.report_type === 'qweb-pdf') {
|
||||
// Trigger the download of the pdf/controller report
|
||||
|
||||
if (action.report_type == 'qweb-pdf') {
|
||||
(wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
|
||||
// Fallback of qweb-pdf if wkhtmltopdf is not installed
|
||||
if (presence == 'install' && action.report_type == 'qweb-pdf') {
|
||||
self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
|
||||
system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\
|
||||
wkhtmltopdf.org</a>'), true);
|
||||
report_url = report_url.substring(12)
|
||||
window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
|
||||
instance.web.unblockUI();
|
||||
return;
|
||||
} 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\
|
||||
support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
|
||||
target="_blank">wkhtmltopdf.org</a>'), true);
|
||||
}
|
||||
}
|
||||
return trigger_download(self.session, response, c);
|
||||
});
|
||||
} else if (action.report_type == 'controller') {
|
||||
(wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
|
||||
// Fallback on html if wkhtmltopdf is not installed or if OpenERP is started with one worker
|
||||
if (presence === 'install') {
|
||||
self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
|
||||
system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\
|
||||
wkhtmltopdf.org</a>'), true);
|
||||
report_url = report_url.substring(12)
|
||||
window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
|
||||
instance.web.unblockUI();
|
||||
return;
|
||||
} else if (presence === 'workers') {
|
||||
self.do_notify(_t('Report'), _t('You need to start OpenERP with at least two \
|
||||
workers to print a pdf version of the reports.'), true);
|
||||
report_url = report_url.substring(12)
|
||||
window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
|
||||
instance.web.unblockUI();
|
||||
return;
|
||||
} 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\
|
||||
support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
|
||||
target="_blank">wkhtmltopdf.org</a>'), true);
|
||||
}
|
||||
return trigger_download(self.session, response, c);
|
||||
}
|
||||
});
|
||||
} else if (action.report_type === 'controller') {
|
||||
return trigger_download(self.session, response, c);
|
||||
}
|
||||
} else {
|
||||
return self._super(action, options);
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
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-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>
|
||||
<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-set="additional_title" t-value="main_object.name"/>
|
||||
</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
|
||||
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
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<t t-call="report.external_layout">
|
||||
<div class="page">
|
||||
<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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
|
||||
<span t-field="o.partner_id.vat"/>
|
||||
|
|
|
@ -156,8 +156,8 @@ class WebKitParser(report_sxw):
|
|||
"""Call webkit in order to generate pdf"""
|
||||
if not webkit_header:
|
||||
webkit_header = report_xml.webkit_header
|
||||
tmp_dir = tempfile.gettempdir()
|
||||
out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.")
|
||||
fd, out_filename = tempfile.mkstemp(suffix=".pdf",
|
||||
prefix="webkit.tmp.")
|
||||
file_to_del = [out_filename]
|
||||
if 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.
|
||||
command.extend(['--encoding', 'utf-8'])
|
||||
if header :
|
||||
head_file = file( os.path.join(
|
||||
tmp_dir,
|
||||
str(time.time()) + '.head.html'
|
||||
),
|
||||
'w'
|
||||
)
|
||||
head_file.write(self._sanitize_html(header.encode('utf-8')))
|
||||
head_file.close()
|
||||
with tempfile.NamedTemporaryFile(suffix=".head.html",
|
||||
delete=False) as head_file:
|
||||
head_file.write(self._sanitize_html(header.encode('utf-8')))
|
||||
file_to_del.append(head_file.name)
|
||||
command.extend(['--header-html', head_file.name])
|
||||
if footer :
|
||||
foot_file = file( os.path.join(
|
||||
tmp_dir,
|
||||
str(time.time()) + '.foot.html'
|
||||
),
|
||||
'w'
|
||||
)
|
||||
foot_file.write(self._sanitize_html(footer.encode('utf-8')))
|
||||
foot_file.close()
|
||||
with tempfile.NamedTemporaryFile(suffix=".foot.html",
|
||||
delete=False) as foot_file:
|
||||
foot_file.write(self._sanitize_html(footer.encode('utf-8')))
|
||||
file_to_del.append(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(',', '.')])
|
||||
count = 0
|
||||
for html in html_list :
|
||||
html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
|
||||
count += 1
|
||||
html_file.write(self._sanitize_html(html.encode('utf-8')))
|
||||
html_file.close()
|
||||
with tempfile.NamedTemporaryFile(suffix="%d.body.html" %count,
|
||||
delete=False) as html_file:
|
||||
count += 1
|
||||
html_file.write(self._sanitize_html(html.encode('utf-8')))
|
||||
file_to_del.append(html_file.name)
|
||||
command.append(html_file.name)
|
||||
command.append(out_filename)
|
||||
|
@ -227,9 +217,9 @@ class WebKitParser(report_sxw):
|
|||
if status :
|
||||
raise except_osv(_('Webkit error' ),
|
||||
_("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
|
||||
pdf_file = open(out_filename, 'rb')
|
||||
pdf = pdf_file.read()
|
||||
pdf_file.close()
|
||||
with open(out_filename, 'rb') as pdf_file:
|
||||
pdf = pdf_file.read()
|
||||
os.close(fd)
|
||||
finally:
|
||||
if stderr_fd is not None:
|
||||
os.close(stderr_fd)
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
|
||||
</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"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
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_page_public,survey.page public,model_survey_page,base.group_public,1,0,0,0
|
||||
access_survey_question_public,survey.question public,model_survey_question,base.group_public,1,0,0,0
|
||||
access_survey_label_public,survey.label public,model_survey_label,base.group_public,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_line_public,survey.user_input_line public,model_survey_user_input_line,base.group_public,1,1,1,0
|
||||
access_survey_stage_public,survey.stage public,model_survey_stage,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,,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,,1,0,0,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,,1,1,1,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_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
|
||||
|
|
|
|
@ -2596,6 +2596,7 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
|
|||
this.$('.select_records').on('click', self.on_click);
|
||||
},
|
||||
on_click: function(ev) {
|
||||
event.preventDefault();
|
||||
var self = this;
|
||||
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
|
||||
this.pop = new instance.web.form.SelectCreatePopup(this);
|
||||
|
@ -2614,15 +2615,14 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
|
|||
});
|
||||
}
|
||||
else {
|
||||
var domain = ["id", "in", element_ids];
|
||||
var domain = [["id", "in", element_ids]];
|
||||
var domain_done = $.Deferred().resolve(domain);
|
||||
}
|
||||
$.when(domain_done).then(function (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'
|
||||
});
|
||||
}
|
||||
else if (values.length == 0)
|
||||
values.push({
|
||||
label: _t("No results to show..."),
|
||||
action: function() {},
|
||||
classname: 'oe_m2o_dropdown_option'
|
||||
});
|
||||
|
||||
return values;
|
||||
});
|
||||
|
@ -4457,7 +4463,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
|||
else
|
||||
return $.when();
|
||||
}).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);
|
||||
}else {
|
||||
self.handle_button(name, id, function(){
|
||||
|
|
|
@ -436,6 +436,16 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
ir_actions_act_window: function (action, options) {
|
||||
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({
|
||||
widget: function () { return new instance.web.ViewManagerAction(self, action); },
|
||||
action: action,
|
||||
|
|
|
@ -108,8 +108,11 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
locs = request.website.enumerate_pages()
|
||||
while True:
|
||||
start = pages * LOC_PER_SITEMAP
|
||||
loc_slice = islice(locs, start, start + LOC_PER_SITEMAP)
|
||||
urls = iuv.render(cr, uid, 'website.sitemap_locs', dict(locs=loc_slice), context=context)
|
||||
values = {
|
||||
'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():
|
||||
page = iuv.render(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context)
|
||||
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)
|
||||
def theme_change(self, theme_id=False, **kwargs):
|
||||
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')
|
||||
views = view.search(
|
||||
request.cr, request.uid, [('inherit_id', '=', view_option_id)],
|
||||
context=request.context)
|
||||
view.write(request.cr, request.uid, views, {'inherit_id': False},
|
||||
context=request.context)
|
||||
views = Views.search(request.cr, request.uid, [
|
||||
('inherit_id', '=', theme_template_id),
|
||||
('application', '=', 'enabled'),
|
||||
], context=request.context)
|
||||
Views.write(request.cr, request.uid, views, {
|
||||
'application': 'disabled',
|
||||
}, context=request.context)
|
||||
|
||||
if theme_id:
|
||||
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)
|
||||
view.write(request.cr, request.uid, [view_id],
|
||||
{'inherit_id': view_option_id}, context=request.context)
|
||||
Views.write(request.cr, request.uid, [view_id], {
|
||||
'application': 'enabled'
|
||||
}, context=request.context)
|
||||
|
||||
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)
|
||||
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)
|
||||
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']
|
||||
view_model, view_theme_id = imd.get_object_reference(
|
||||
request.cr, request.uid, 'website', 'theme')
|
||||
|
||||
user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context)
|
||||
group_ids = [g.id for g in user.groups_id]
|
||||
user = request.registry['res.users']\
|
||||
.browse(request.cr, request.uid, request.uid, request.context)
|
||||
user_groups = set(user.groups_id)
|
||||
|
||||
view = request.registry.get("ir.ui.view")
|
||||
views = view._views_get(request.cr, request.uid, xml_id, context=request.context)
|
||||
done = {}
|
||||
views = request.registry["ir.ui.view"]\
|
||||
._views_get(request.cr, request.uid, xml_id, context=request.context)
|
||||
done = set()
|
||||
result = []
|
||||
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
|
||||
if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
|
||||
if v.inherit_option_id.id not in done:
|
||||
if full or (v.application != 'always' and v.inherit_id.id != view_theme_id):
|
||||
if v.inherit_id not in done:
|
||||
result.append({
|
||||
'name': v.inherit_option_id.name,
|
||||
'name': v.inherit_id.name,
|
||||
'id': v.id,
|
||||
'xml_id': v.xml_id,
|
||||
'inherit_id': v.inherit_id.id,
|
||||
'header': True,
|
||||
'active': False
|
||||
})
|
||||
done[v.inherit_option_id.id] = True
|
||||
done.add(v.inherit_id)
|
||||
result.append({
|
||||
'name': v.name,
|
||||
'id': v.id,
|
||||
'xml_id': v.xml_id,
|
||||
'inherit_id': v.inherit_id.id,
|
||||
'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
|
||||
|
||||
|
@ -381,7 +378,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
"""
|
||||
response = werkzeug.wrappers.Response()
|
||||
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)
|
||||
|
||||
|
||||
#------------------------------------------------------
|
||||
|
|
|
@ -141,49 +141,50 @@ class ir_http(orm.AbstractModel):
|
|||
return response
|
||||
|
||||
def _handle_exception(self, exception=None, code=500):
|
||||
if isinstance(exception, werkzeug.exceptions.HTTPException) and hasattr(exception, 'response') and exception.response:
|
||||
return exception.response
|
||||
try:
|
||||
return super(ir_http, self)._handle_exception(exception)
|
||||
except Exception:
|
||||
|
||||
attach = self._serve_attachment()
|
||||
if attach:
|
||||
return attach
|
||||
attach = self._serve_attachment()
|
||||
if attach:
|
||||
return attach
|
||||
|
||||
if getattr(request, 'website_enabled', False) and request.website:
|
||||
values = dict(
|
||||
exception=exception,
|
||||
traceback=traceback.format_exc(exception),
|
||||
)
|
||||
if exception:
|
||||
code = getattr(exception, 'code', code)
|
||||
if isinstance(exception, ir_qweb.QWebException):
|
||||
values.update(qweb_exception=exception)
|
||||
if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
|
||||
code = 403
|
||||
if code == 500:
|
||||
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
|
||||
if 'qweb_exception' in values:
|
||||
view = request.registry.get("ir.ui.view")
|
||||
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]
|
||||
values['views'] = to_reset
|
||||
elif code == 403:
|
||||
logger.warn("403 Forbidden:\n\n%s", values['traceback'])
|
||||
if getattr(request, 'website_enabled', False) and request.website:
|
||||
values = dict(
|
||||
exception=exception,
|
||||
traceback=traceback.format_exc(exception),
|
||||
)
|
||||
if exception:
|
||||
code = getattr(exception, 'code', code)
|
||||
if isinstance(exception, ir_qweb.QWebException):
|
||||
values.update(qweb_exception=exception)
|
||||
if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
|
||||
code = 403
|
||||
if code == 500:
|
||||
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
|
||||
if 'qweb_exception' in values:
|
||||
view = request.registry.get("ir.ui.view")
|
||||
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]
|
||||
values['views'] = to_reset
|
||||
elif code == 403:
|
||||
logger.warn("403 Forbidden:\n\n%s", values['traceback'])
|
||||
|
||||
values.update(
|
||||
status_message=werkzeug.http.HTTP_STATUS_CODES[code],
|
||||
status_code=code,
|
||||
)
|
||||
values.update(
|
||||
status_message=werkzeug.http.HTTP_STATUS_CODES[code],
|
||||
status_code=code,
|
||||
)
|
||||
|
||||
if not request.uid:
|
||||
self._auth_method_public()
|
||||
if not request.uid:
|
||||
self._auth_method_public()
|
||||
|
||||
try:
|
||||
html = request.website._render('website.%s' % code, values)
|
||||
except Exception:
|
||||
html = request.website._render('website.http_error', values)
|
||||
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
|
||||
try:
|
||||
html = request.website._render('website.%s' % code, values)
|
||||
except Exception:
|
||||
html = request.website._render('website.http_error', values)
|
||||
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):
|
||||
def __init__(self, url_map, model=False, domain='[]'):
|
||||
|
|
|
@ -15,6 +15,7 @@ import urllib2
|
|||
import urlparse
|
||||
import re
|
||||
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
from dateutil import parser
|
||||
from lxml import etree, html
|
||||
|
@ -265,10 +266,19 @@ class Image(orm.AbstractModel):
|
|||
if options is None: options = {}
|
||||
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)),
|
||||
record._model._name,
|
||||
field_name, record.id))
|
||||
werkzeug.urls.url_encode(url_params)
|
||||
))
|
||||
|
||||
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
|
|
|
@ -13,8 +13,6 @@ from openerp.osv import osv, fields
|
|||
class view(osv.osv):
|
||||
_inherit = "ir.ui.view"
|
||||
_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)"),
|
||||
'website_meta_title': fields.char("Website meta title", size=70, translate=True),
|
||||
'website_meta_description': fields.text("Website meta description", size=160, translate=True),
|
||||
|
@ -24,25 +22,30 @@ class view(osv.osv):
|
|||
'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
|
||||
# Used by translation mechanism, SEO and optional templates
|
||||
def _views_get(self, cr, uid, view, options=True, context=None, root=True, stack_result=None):
|
||||
if not context:
|
||||
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
|
||||
def _views_get(self, cr, uid, view_id, options=True, context=None, root=True):
|
||||
""" For a given view ``view_id``, should return:
|
||||
|
||||
* 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:
|
||||
view = view_obj(view)
|
||||
view = self._view_obj(cr, uid, view_id, context=context)
|
||||
except ValueError:
|
||||
# Shall we log that ?
|
||||
return []
|
||||
|
@ -55,19 +58,25 @@ class view(osv.osv):
|
|||
node = etree.fromstring(view.arch)
|
||||
for child in node.xpath("//t[@t-call]"):
|
||||
try:
|
||||
call_view = view_obj(child.get('t-call'))
|
||||
called_view = self._view_obj(cr, uid, child.get('t-call'), context=context)
|
||||
except ValueError:
|
||||
continue
|
||||
if call_view not in result:
|
||||
result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result)
|
||||
if called_view not in result:
|
||||
result += self._views_get(cr, uid, called_view, options=options, context=context)
|
||||
|
||||
todo = view.inherit_children_ids
|
||||
if options:
|
||||
todo += filter(lambda x: not x.inherit_id, view.inherited_option_ids)
|
||||
# Keep options in a determinitic order whatever their enabled disabled status
|
||||
todo.sort(lambda x,y:cmp(x.id,y.id))
|
||||
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):
|
||||
extensions = view.inherit_children_ids
|
||||
if not options:
|
||||
# only active children
|
||||
extensions = (v for v in view.inherit_children_ids
|
||||
if v.application in ('always', 'enabled'))
|
||||
|
||||
# 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:
|
||||
result.append(r)
|
||||
return result
|
||||
|
|
|
@ -541,7 +541,10 @@ class website(osv.osv):
|
|||
response.mimetype = Image.MIME[image.format]
|
||||
|
||||
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:
|
||||
response.data = data
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
var viewId = $(document.documentElement).data('view-xmlid');
|
||||
openerp.jsonRpc('/website/customize_template_get', 'call', {
|
||||
'xml_id': viewId,
|
||||
'optional': false,
|
||||
'full': true,
|
||||
}).then(function (views) {
|
||||
self.loadViews.call(self, views);
|
||||
self.open.call(self);
|
||||
|
|
|
@ -470,8 +470,14 @@
|
|||
});
|
||||
menu.on('click', 'a[data-action!=ace]', function (event) {
|
||||
var view_id = $(event.currentTarget).data('view-id');
|
||||
openerp.jsonRpc('/website/customize_template_toggle', 'call', {
|
||||
'view_id': view_id
|
||||
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'ir.ui.view',
|
||||
method: 'toggle',
|
||||
args: [],
|
||||
kwargs: {
|
||||
ids: [parseInt(view_id, 10)],
|
||||
context: website.get_context()
|
||||
}
|
||||
}).then( function() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
|
|
@ -264,21 +264,21 @@
|
|||
},
|
||||
description: function () {
|
||||
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) {
|
||||
// TODO create tag if missing
|
||||
$('meta[name=description]').attr('value', description);
|
||||
$('meta[name=description]').attr('content', description);
|
||||
this.trigger('description-changed', description);
|
||||
},
|
||||
keywords: function () {
|
||||
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: [];
|
||||
},
|
||||
changeKeywords: function (keywords) {
|
||||
// TODO create tag if missing
|
||||
$('meta[name=keywords]').attr('value', keywords.join(","));
|
||||
$('meta[name=keywords]').attr('content', keywords.join(","));
|
||||
this.trigger('keywords-changed', keywords);
|
||||
},
|
||||
headers: function (tag) {
|
||||
|
@ -296,7 +296,7 @@
|
|||
});
|
||||
},
|
||||
company: function () {
|
||||
return $('meta[name="openerp.company"]').attr('value');
|
||||
return $('html').attr('data-oe-company-name');
|
||||
},
|
||||
bodyText: function () {
|
||||
return $('body').children().not('.js_seo_configuration').text();
|
||||
|
|
|
@ -203,82 +203,82 @@
|
|||
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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cerulean.min.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/united.min.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</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">
|
||||
<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"/>
|
||||
|
|
|
@ -61,8 +61,10 @@
|
|||
t-att-data-editable="'1' if editable else None"
|
||||
t-att-data-translatable="'1' if translatable else None"
|
||||
t-att-data-view-xmlid="xmlid if editable else None"
|
||||
t-att-data-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>
|
||||
<meta charset="utf-8" />
|
||||
<t t-if="main_object and 'website_meta_title' in main_object">
|
||||
<t t-set="title" t-value="main_object.website_meta_title"/>
|
||||
</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>
|
||||
<meta name="viewport" content="initial-scale=1"/>
|
||||
<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-content="main_object and 'website_meta_description' in main_object
|
||||
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"/>
|
||||
<title><t t-esc="title"/></title>
|
||||
|
||||
|
@ -137,14 +138,14 @@
|
|||
<footer>
|
||||
<div class="container hidden-print" id="footer_container">
|
||||
<div class="row">
|
||||
<div class="col-md-3" name="product">
|
||||
<div class="col-md-3">
|
||||
<h4>Our products & Services</h4>
|
||||
<ul class="list-unstyled" name="products">
|
||||
<li><a href="/">Home</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-3" name="info">
|
||||
<h4 name="info_title">Connect with us</h4>
|
||||
<h4>Connect with us</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="/page/website.contactus">Contact us</a></li>
|
||||
</ul>
|
||||
|
@ -161,7 +162,7 @@
|
|||
<a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-md-5 col-lg-offset-1" name="about_us">
|
||||
<div class="col-md-5 col-lg-offset-1">
|
||||
<div>
|
||||
<h4>
|
||||
<span t-field="res_company.name">Your Company</span>
|
||||
|
@ -220,7 +221,7 @@
|
|||
</html>
|
||||
</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">
|
||||
<a href="/" class="navbar-brand logo">
|
||||
<img src="/logo.png"/>
|
||||
|
@ -304,9 +305,9 @@
|
|||
<script type="text/javascript" src="/website/static/src/js/jQuery.transfo.js"></script>
|
||||
</template>
|
||||
|
||||
<template id="debugger" inherit_option_id="website.layout" name="Debugger & Tests">
|
||||
<template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger & Tests">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
@ -318,7 +319,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<li class="divider"/>
|
||||
<li>
|
||||
|
@ -329,7 +330,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<div class="oe_structure">
|
||||
<section data-snippet-id='three-columns' class="mt16 mb16">
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
|
||||
<!-- Option: Blog Post List: show 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">
|
||||
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
|
||||
<span class="fa fa-tags"/>
|
||||
|
@ -265,7 +265,7 @@
|
|||
|
||||
<!-- Options: Blog Post: 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">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
@ -285,7 +285,7 @@
|
|||
|
||||
<!-- Options: Blog Post: user can reply -->
|
||||
<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">
|
||||
<xpath expr="//ul[@id='comments-list']" position="before">
|
||||
<section class="mb32 read_width css_editable_mode_hidden">
|
||||
|
@ -307,7 +307,7 @@
|
|||
|
||||
<!-- Options: Blog Post: user can select text for 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">
|
||||
<attribute name="class">js_tweet mt32</attribute>
|
||||
</xpath>
|
||||
|
@ -318,7 +318,7 @@
|
|||
|
||||
<!-- Options: Blog Post: user can add Inline Discussion -->
|
||||
<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">
|
||||
<attribute name="enable_chatter_discuss">True</attribute>
|
||||
</xpath>
|
||||
|
@ -326,7 +326,7 @@
|
|||
|
||||
<!-- Options: Blog Post: show 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">
|
||||
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
|
||||
<span class="fa fa-tags"/>
|
||||
|
@ -355,7 +355,7 @@
|
|||
<!-- Option:Right Column for extra info -->
|
||||
|
||||
<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">
|
||||
<attribute name="class">col-sm-8</attribute>
|
||||
</xpath>
|
||||
|
@ -366,7 +366,7 @@
|
|||
|
||||
<!-- Option:Right Column: 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">
|
||||
<section class="mt32">
|
||||
<h4>Tags</h4>
|
||||
|
@ -383,7 +383,7 @@
|
|||
|
||||
<!-- Option:Right Column: 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">
|
||||
<section class="mt32">
|
||||
<h4>Archives</h4>
|
||||
|
@ -400,7 +400,7 @@
|
|||
|
||||
<!-- Option:Right Column: about us -->
|
||||
<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">
|
||||
<section class="mt32">
|
||||
<h4>About us</h4>
|
||||
|
@ -417,7 +417,7 @@
|
|||
|
||||
<!-- Option:Right Column: follow us -->
|
||||
<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">
|
||||
<section class="mt32">
|
||||
<h4>Follow us<small t-if="blog">: <t t-esc="blog.name"/></small></h4>
|
||||
|
@ -444,7 +444,7 @@
|
|||
|
||||
<!-- Option:Right Column: blogs -->
|
||||
<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">
|
||||
<section class="mt32 mb32">
|
||||
<h4>Our Blogs</h4>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<openerp>
|
||||
<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">
|
||||
<form action="/crm/contactus" method="post" class="form-horizontal mt32" enctype="multipart/form-data">
|
||||
<t t-foreach="kwargs" t-as="kwarg">
|
||||
|
@ -47,7 +47,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<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>
|
||||
|
|
|
@ -86,8 +86,8 @@
|
|||
</t>
|
||||
<div class="media">
|
||||
<a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"
|
||||
t-field="partner.image_small"
|
||||
t-field-options='{"widget": "image", "class": "media-object"}'
|
||||
t-field="partner.image"
|
||||
t-field-options='{"widget": "image", "class": "media-object", "max_width": 128}'
|
||||
></a>
|
||||
<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&' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}">
|
||||
|
@ -113,7 +113,7 @@
|
|||
</t>
|
||||
</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">
|
||||
<h3>World Map</h3>
|
||||
<ul class="nav">
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
</template>
|
||||
|
||||
<!-- 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">
|
||||
|
||||
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&partner_url=/customers/&output=embed"
|
||||
|
@ -74,7 +74,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<h3>References by Country</h3>
|
||||
<ul class="nav nav-pills nav-stacked mt16 mb32">
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
</t>
|
||||
</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">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16">
|
||||
|
@ -106,7 +106,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16">
|
||||
|
@ -123,7 +123,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16 mt16 country_events">
|
||||
|
@ -140,7 +140,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<attribute name="class">col-md-6</attribute>
|
||||
</xpath>
|
||||
|
@ -159,7 +159,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<ul class="nav nav-pills nav-stacked mt32">
|
||||
<t t-foreach="types">
|
||||
|
@ -173,7 +173,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<ul class="nav nav-pills nav-stacked mt32">
|
||||
<t t-foreach="countries">
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
{
|
||||
title: "Pay Now",
|
||||
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",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<data>
|
||||
|
||||
<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>
|
||||
</xpath>
|
||||
</template>
|
||||
|
@ -26,7 +26,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<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">
|
||||
|
|
|
@ -88,10 +88,21 @@ class website_event(http.Controller):
|
|||
days_tracks_count[day] = len(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", {
|
||||
'event': event,
|
||||
'days': days,
|
||||
'days_nbr': days_tracks_count,
|
||||
'speakers': speakers,
|
||||
'tag': tag
|
||||
})
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<openerp>
|
||||
<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">
|
||||
<t t-set="head">
|
||||
<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) }">
|
||||
<span t-esc="track and track.name"/>
|
||||
</a>
|
||||
<div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker">
|
||||
<small t-esc="speaker.display_name"/>
|
||||
<div class="text-muted">
|
||||
<small t-esc="speakers[track.id]"/>
|
||||
</div>
|
||||
</t>
|
||||
</td>
|
||||
|
@ -98,11 +98,11 @@
|
|||
<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 ''}">
|
||||
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
|
||||
<span t-esc="track.name"/><br/>
|
||||
<div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker">
|
||||
<small t-esc="speaker.display_name"/>
|
||||
</div>
|
||||
<span t-esc="track.name"/>
|
||||
</a>
|
||||
<div class="text-muted">
|
||||
<small t-esc="speakers[track.id]"/>
|
||||
</div>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
|
@ -157,7 +157,7 @@
|
|||
</t>
|
||||
</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">
|
||||
<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>
|
||||
|
@ -246,7 +246,7 @@
|
|||
</t>
|
||||
</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">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
|
|
@ -330,13 +330,12 @@ class WebsiteForum(http.Controller):
|
|||
cr, uid, context = request.cr, request.uid, request.context
|
||||
if kwargs.get('comment') and post.forum_id.id == forum.id:
|
||||
# TDE FIXME: check that post_id is the question or one of its answers
|
||||
if request.registry['res.users'].has_group(cr, uid, 'website_mail.group_comment'):
|
||||
request.registry['forum.post'].message_post(
|
||||
cr, uid, post.id,
|
||||
body=kwargs.get('comment'),
|
||||
type='comment',
|
||||
subtype='mt_comment',
|
||||
context=dict(context, mail_create_nosubcribe=True))
|
||||
request.registry['forum.post'].message_post(
|
||||
cr, uid, post.id,
|
||||
body=kwargs.get('comment'),
|
||||
type='comment',
|
||||
subtype='mt_comment',
|
||||
context=dict(context, mail_create_nosubcribe=True))
|
||||
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)
|
||||
|
@ -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)
|
||||
|
||||
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'
|
||||
|
||||
values = self._prepare_forum_values(forum=forum, searches=searches)
|
||||
|
|
|
@ -725,11 +725,8 @@
|
|||
|
||||
<template id="users">
|
||||
<t t-call="website_forum.header">
|
||||
<t t-foreach="users" t-as="user">
|
||||
<t t-if="user_index % 3 == 0">
|
||||
<div class="row"></div>
|
||||
</t>
|
||||
<div class="col-sm-4">
|
||||
<div t-foreach="users" t-as="row_users" class="row mt16">
|
||||
<div t-foreach="row_users" t-as="user" class="col-sm-4">
|
||||
<img class="pull-left img img-circle img-avatar" t-attf-src="/forum/user/#{user.id}/avatar"/>
|
||||
<div>
|
||||
<a t-attf-href="/forum/#{slug(forum)}/user/#{user.id}" t-field="user.name"/>
|
||||
|
@ -750,7 +747,7 @@
|
|||
<t t-raw="0"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
<t t-call="website.pager"/>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<data>
|
||||
<!-- 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">
|
||||
<section class="container">
|
||||
<div class="col-sm-12 text-center" t-if="len(employee_ids)">
|
||||
|
|
|
@ -11,6 +11,26 @@
|
|||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</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">
|
||||
<field name="name">Job department: Public</field>
|
||||
<field name="model_id" ref="hr.model_hr_department"/>
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
</t>
|
||||
</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">
|
||||
<ul class="nav nav-pills nav-stacked mb32">
|
||||
<li t-att-class=" '' if department_id else 'active' "><a href="/jobs">All Departments</a></li>
|
||||
|
@ -249,7 +249,7 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<ul class="nav nav-pills nav-stacked mb32">
|
||||
<li t-att-class=" '' if office_id else 'active' "><a href="/jobs">All Offices</a></li>
|
||||
|
|
|
@ -62,6 +62,7 @@ class WebsiteMail(http.Controller):
|
|||
|
||||
@http.route(['/website_mail/is_follower'], type='json', auth="public", website=True)
|
||||
def call(self, model, id, **post):
|
||||
id = int(id)
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
|
||||
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
|
||||
elif request.session.get('partner_id'):
|
||||
partner_id = partner_obj.browse(cr, SUPERUSER_ID, request.session.get('partner_id'), context)
|
||||
|
||||
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]
|
||||
email = partner_id and partner_id.email or ""
|
||||
|
||||
return {
|
||||
values = {
|
||||
'is_user': uid != public_id,
|
||||
'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
|
||||
|
|
|
@ -34,7 +34,7 @@ class MailMessage(osv.Model):
|
|||
res[message.id] = message.subject
|
||||
else:
|
||||
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
|
||||
|
||||
_columns = {
|
||||
|
|
|
@ -7,17 +7,20 @@
|
|||
selector: ".js_follow",
|
||||
start: function (editable_mode) {
|
||||
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', {
|
||||
model: this.$target.data('object'),
|
||||
id: +this.$target.data('id'),
|
||||
id: this.$target.data('id'),
|
||||
fields: ['name', 'alias_id'],
|
||||
}).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')
|
||||
.val(data.email ? data.email : "")
|
||||
.attr("disabled", data.is_follower && data.email.length ? "disabled" : false);
|
||||
self.$target.attr("data-follow", data.is_follower ? 'on' : 'off');
|
||||
.attr("disabled", data.is_follower || (data.email.length && self.is_user) ? "disabled" : false);
|
||||
self.$target.removeClass("hidden");
|
||||
});
|
||||
|
||||
|
@ -30,10 +33,11 @@
|
|||
self.on_click();
|
||||
});
|
||||
}
|
||||
return;
|
||||
},
|
||||
on_click: function () {
|
||||
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(/.+@.+/)) {
|
||||
this.$target.addClass('has-error');
|
||||
|
@ -47,13 +51,27 @@
|
|||
'message_is_follower': this.$target.attr("data-follow") || "off",
|
||||
'email': $email.length ? $email.val() : false,
|
||||
}).then(function (follow) {
|
||||
if (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');
|
||||
self.toggle_subscription(follow);
|
||||
});
|
||||
},
|
||||
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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -503,20 +503,25 @@
|
|||
<span class="oe_snippet_thumbnail_title">Subscribe Button</span>
|
||||
</div>
|
||||
|
||||
<div class="oe_snippet_body input-group js_follow"
|
||||
<div class="oe_snippet_body js_follow"
|
||||
data-id="0"
|
||||
data-object="mail.group"
|
||||
data-follow="off">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
class="js_follow_email form-control"
|
||||
placeholder="your email..."/>
|
||||
<span class="input-group-btn">
|
||||
<a href="#" class="btn btn-default js_unfollow_btn">Unsubscribe</a>
|
||||
<a href="#" class="btn btn-primary js_follow_btn">Subscribe</a>
|
||||
</span>
|
||||
<div class="alert alert-success hidden">thanks for your subscription!</div>
|
||||
<div class="input-group js_mg_follow_form">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
class="js_follow_email form-control"
|
||||
placeholder="your email..."/>
|
||||
<span class="input-group-btn">
|
||||
<button href="#" class="btn btn-primary js_follow_btn">Subscribe</button>
|
||||
</span>
|
||||
</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>
|
||||
|
|
|
@ -9,13 +9,14 @@ from openerp.addons.web.http import request
|
|||
|
||||
|
||||
class MailGroup(http.Controller):
|
||||
_thread_per_page = 10
|
||||
_thread_per_page = 20
|
||||
_replies_per_page = 10
|
||||
|
||||
def _get_archives(self, group_id):
|
||||
MailMessage = request.registry['mail.message']
|
||||
groups = MailMessage.read_group(
|
||||
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:
|
||||
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()
|
||||
|
@ -27,7 +28,7 @@ class MailGroup(http.Controller):
|
|||
def view(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
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)}
|
||||
return request.website.render('website_mail_group.mail_groups', values)
|
||||
|
||||
|
@ -73,6 +74,7 @@ class MailGroup(http.Controller):
|
|||
'archives': self._get_archives(group.id),
|
||||
'date_begin': date_begin,
|
||||
'date_end': date_end,
|
||||
'replies_per_page': self._replies_per_page,
|
||||
}
|
||||
return request.website.render('website_mail_group.group_messages', values)
|
||||
|
||||
|
@ -81,11 +83,49 @@ class MailGroup(http.Controller):
|
|||
], type='http', auth="public", website=True)
|
||||
def thread_discussion(self, group, message, mode='thread', date_begin=None, date_end=None, **post):
|
||||
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 = {
|
||||
'message': message,
|
||||
'group': group,
|
||||
'mode': mode,
|
||||
'archives': self._get_archives(group.id),
|
||||
'date_begin': date_begin,
|
||||
'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)
|
||||
|
||||
@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)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.o_mg_avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.o_mg_link_show {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_mg_link_content {
|
||||
display: none;
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,152 +1,240 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<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&field=image_small&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">
|
||||
<t t-call="website.layout">
|
||||
<section class="container">
|
||||
<div class="row mt8">
|
||||
<div class="col-md-5 mt16">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/groups">Mailing Lists</a></li>
|
||||
<li class="active" t-esc="group.name"/>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-5 pull-right">
|
||||
<t t-call="website.pager"/>
|
||||
</div>
|
||||
</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&date_begin=#{ month_archive['date_begin'] }&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>
|
||||
<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 id="wrap" class="oe_structure oe_empty">
|
||||
<section class="bg-primary jumbotron mt0 mb0">
|
||||
<div class="container">
|
||||
<h1>Stay in touch with our Community</h1>
|
||||
<p>Alone we can do so little, together we can do so much</p>
|
||||
</div>
|
||||
</section>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="group_message">
|
||||
<t t-call="website.layout">
|
||||
<div class="container">
|
||||
<div class="row mt8">
|
||||
<div class="col-md-5">
|
||||
<ol class="breadcrumb mb8">
|
||||
<li><a href="/groups">Mailing Lists</a></li>
|
||||
<li>
|
||||
<a t-attf-href="/groups/#{slug(group)}?mode=#{mode}&date_begin=#{date_begin}&date_end=#{date_end}">
|
||||
<span t-field="group.name"/>
|
||||
</a>
|
||||
</li>
|
||||
<li class="active" t-esc="message.subject or 'Message'"/>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt32">
|
||||
<div class="row mt8" t-foreach="groups" t-as="group">
|
||||
<div class="col-md-3">
|
||||
<img t-att-src="'/website/image?model=mail.group&field=image_small&id='+str(group['id'])" class="pull-left"/>
|
||||
<strong><a t-attf-href="/groups/#{ slug(group) }" t-esc="group.name"/></strong><br />
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<h1 t-field="message.subject"/>
|
||||
<img class="img-rounded pull-left" t-att-src="'/website/image?model=mail.message&field=author_avatar&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&id='+str(message.id)+'&method=download_attachment&attachment_id='+str(attachment.id)" target="_blank">
|
||||
<t t-if="attachment.file_type == 'webimage'">
|
||||
<img t-att-src="'/web/binary/image?model=ir.attachment&field=datas&id=' + str(attachment.id) + '&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 class="col-md-4">
|
||||
<div t-esc="group.description" class="text-muted"/>
|
||||
</div>
|
||||
<div t-if="message.child_ids">
|
||||
<h2 class="page-header">Follow ups</h2>
|
||||
<t t-set="messages" t-value="message.child_ids"/>
|
||||
<t t-call="website_mail_group.messages_short"/>
|
||||
<div class="col-md-2">
|
||||
<i class='fa fa-user'/> <t t-esc="len(group.message_follower_ids)"/> participants<br />
|
||||
<i class='fa fa-envelope-o'/> <t t-esc="len(group.message_ids)"/> messages
|
||||
</div>
|
||||
<div t-if="message.parent_id">
|
||||
<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>
|
||||
<div class="col-md-3">
|
||||
<t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</div>
|
||||
</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 '&date_begin=%s' % date_begin or ''}#{date_end and '&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&date_begin=#{ month_archive['date_begin'] }&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 '&date_begin=%s' % date_begin or ''}#{date_end and '&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&date_begin=#{ month_archive['date_begin'] }&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&field=author_avatar&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&id='+str(message.id)+'&method=download_attachment&attachment_id='+str(attachment.id)" target="_blank">
|
||||
<t t-if="attachment.file_type == 'webimage'">
|
||||
<img t-att-src="'/web/binary/image?model=ir.attachment&field=datas&id=' + str(attachment.id) + '&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">
|
||||
<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&field=author_avatar&id='+str(thread.id)"/>
|
||||
<div class="media-body">
|
||||
<h4 class="media-heading">
|
||||
|
@ -161,14 +249,36 @@
|
|||
}'/>
|
||||
</t>
|
||||
<t t-if="not thread.author_id"><t t-esc="thread.email_from"/></t>
|
||||
<span class="fa fa-comment-o">
|
||||
<t t-raw="len(thread.child_ids)"/> replies
|
||||
</span>
|
||||
- <i class="fa fa-calendar"/> <t t-esc="thread.date"/>
|
||||
- <i class="fa fa-paperclip"/> <t t-esc="len(thread.attachment_ids)"/>
|
||||
</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>
|
||||
</li>
|
||||
</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>
|
||||
</openerp>
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
</template>
|
||||
|
||||
<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">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li class="nav-header"><h3>Location</h3></li>
|
||||
|
@ -101,7 +101,7 @@
|
|||
|
||||
<!-- Option: index: Left Google 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">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li class="nav-header"><h3>World Map</h3></li>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
</section>
|
||||
</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">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon hidden-print">
|
||||
|
@ -130,7 +130,7 @@
|
|||
</template>
|
||||
|
||||
<!-- 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">
|
||||
<section class="mb32 css_editable_mode_hidden hidden-print">
|
||||
<form id="comment" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/post" method="POST">
|
||||
|
@ -388,7 +388,7 @@
|
|||
</template>
|
||||
|
||||
<!-- 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">
|
||||
<div class="panel panel-default mt16 mb0" id="drawsign">
|
||||
<div class="panel-heading">
|
||||
|
|
|
@ -35,8 +35,8 @@ class table_compute(object):
|
|||
index = 0
|
||||
maxy = 0
|
||||
for p in products:
|
||||
x = p.website_size_x
|
||||
y = p.website_size_y
|
||||
x = min(max(p.website_size_x, 1), PPR)
|
||||
y = min(max(p.website_size_y, 1), PPR)
|
||||
if index>PPG:
|
||||
x = y = 1
|
||||
|
||||
|
@ -490,7 +490,6 @@ class website_sale(http.Controller):
|
|||
values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context)
|
||||
render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now')
|
||||
for acquirer in values['acquirers']:
|
||||
render_ctx['tx_url'] = '/shop/payment/transaction/%s' % acquirer.id
|
||||
acquirer.button = payment_obj.render(
|
||||
cr, SUPERUSER_ID, acquirer.id,
|
||||
order.name,
|
||||
|
@ -504,20 +503,17 @@ class website_sale(http.Controller):
|
|||
|
||||
return request.website.render("website_sale.payment", values)
|
||||
|
||||
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='http', methods=['POST'], auth="public", website=True)
|
||||
def payment_transaction(self, acquirer_id, **post):
|
||||
""" Hook method that creates a payment.transaction and redirect to the
|
||||
acquirer, using post values to re-create the post action.
|
||||
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
|
||||
def payment_transaction(self, acquirer_id):
|
||||
""" Json method that creates a payment.transaction, used to create a
|
||||
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
|
||||
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
|
||||
payment_obj = request.registry.get('payment.acquirer')
|
||||
transaction_obj = request.registry.get('payment.transaction')
|
||||
sale_order_obj = request.registry['sale.order']
|
||||
order = request.website.sale_get_order(context=context)
|
||||
|
@ -529,7 +525,13 @@ class website_sale(http.Controller):
|
|||
|
||||
# find an already existing 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, {
|
||||
'acquirer_id': acquirer_id,
|
||||
'type': 'form',
|
||||
|
@ -541,10 +543,6 @@ class website_sale(http.Controller):
|
|||
'sale_order_id': order.id,
|
||||
}, context=context)
|
||||
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
|
||||
sale_order_obj.write(
|
||||
|
@ -555,9 +553,7 @@ class website_sale(http.Controller):
|
|||
# confirm the quotation
|
||||
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)
|
||||
acquirer_total_url = '%s?%s' % (acquirer_form_post_url, werkzeug.url_encode(post))
|
||||
return request.redirect(acquirer_total_url)
|
||||
return tx_id
|
||||
|
||||
@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):
|
||||
|
|
|
@ -65,6 +65,7 @@ class product_template(osv.Model):
|
|||
_inherit = ["product.template", "website.seo.metadata"]
|
||||
_order = 'website_published desc, website_sequence desc, name'
|
||||
_name = 'product.template'
|
||||
_mail_post_access = 'read'
|
||||
|
||||
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = dict.fromkeys(ids, '')
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
{
|
||||
title: "Pay Now",
|
||||
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",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue