[MERGE] Forward-port of latest saas-4 fixes, up to 0452851

This commit is contained in:
Olivier Dony 2014-05-27 20:49:49 +02:00
commit 04211015fc
63 changed files with 772 additions and 418 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@ -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 = {}

View File

@ -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"/>

View File

@ -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', '&lt;br&gt;')"/>
<span t-field="o.vat"/>

View File

@ -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:

View File

@ -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"/>

View File

@ -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'):

View File

@ -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"/>

View File

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

View File

@ -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

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

View File

@ -192,15 +192,16 @@ class delivery_grid(osv.osv):
weight = 0
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):

View File

@ -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>

View File

@ -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,
}

View File

@ -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>

View File

@ -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

View File

@ -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. */

View File

@ -273,7 +273,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&amp;id=#{widget.author_id[0]}"><t t-esc="widget.author_id[2]"/></a>
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-data-partner="widget.author_id[0]" class="oe_mail_action_author"><t t-esc="widget.author_id[2]"/></a>
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-esc="widget.author_id[2]"/></span>
<t t-if="widget.type == 'notification'">
updated document
@ -293,7 +293,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&amp;id=#{partner[0]}"><t t-esc="partner[1]"/></a>
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}" t-att-data-partner="partner[0]" class="oe_mail_action_author"><t t-esc="partner[1]"/></a>
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
</span>
<t t-if="!partner_last">,</t>

View File

@ -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,

View File

@ -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

View File

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

View File

@ -220,7 +220,7 @@ class purchase_order(osv.osv):
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."),
'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)]},

View File

@ -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>

View File

@ -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>

View File

@ -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"/>

View File

@ -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" />

View File

@ -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()

View File

@ -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);

View File

@ -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

View File

@ -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"/>

View File

@ -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)

View File

@ -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>

View File

@ -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

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

View File

@ -2596,6 +2596,7 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
this.$('.select_records').on('click', self.on_click);
},
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();
},
});
@ -4463,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(){

View File

@ -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,

View File

@ -378,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)
#------------------------------------------------------

View File

@ -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='[]'):

View File

@ -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):

View File

@ -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

View File

@ -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();

View File

@ -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 &amp; 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>
@ -306,7 +307,7 @@
<template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger &amp; 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>

View File

@ -86,8 +86,8 @@
</t>
<div class="media">
<a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"
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&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}">

View File

@ -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>

View File

@ -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
})

View File

@ -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>

View File

@ -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)

View File

@ -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"/>

View File

@ -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

View File

@ -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 = {

View File

@ -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);
});
});
})();

View File

@ -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>

View File

@ -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)

View File

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

View File

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

View File

@ -1,152 +1,240 @@
<?xml version="1.0" encoding="utf-8"?>
<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&amp;field=image_small&amp;id='+str(group['id'])" class="pull-left"/>
<div>
<strong><a t-attf-href="/groups/#{ slug(group) }" t-esc="group.name"/></strong>
<div t-esc="group.description" class="text-muted"/>
<t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="group_messages" name="Message Threads">
<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&amp;date_begin=#{ month_archive['date_begin'] }&amp;date_end=#{month_archive['date_end']}">
<t t-esc="month_archive['date']"/>
<span class="pull-right badge" t-esc="month_archive['date_count']"/>
</a>
</li>
</t>
</ul>
</li>
</ul>
</div>
<div class="col-md-9">
<t t-call="website_mail_group.messages_short"/>
</div>
<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}&amp;date_begin=#{date_begin}&amp;date_end=#{date_end}">
<span t-field="group.name"/>
</a>
</li>
<li class="active" t-esc="message.subject or 'Message'"/>
</ol>
</div>
</div>
<div 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&amp;field=image_small&amp;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&amp;field=author_avatar&amp;id='+str(message.id)" style="width : 30px"/>
<h4 class="mt0 mb32">
<t t-if="message.author_id">
<span t-field="message.author_id" style="display: inline-block;" t-field-options='{
"widget": "contact",
"fields": ["name"]
}'/>
</t>
<t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
on <span t-field="message.date"/>
</h4>
<div t-raw="message.body"/>
<div class="row" t-if="message.attachment_ids">
<h3 class="col-sm-12">Attachment(s):</h3>
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-att-href="'/mail/download_attachment?model=mail.message&amp;id='+str(message.id)+'&amp;method=download_attachment&amp;attachment_id='+str(attachment.id)" target="_blank">
<t t-if="attachment.file_type == 'webimage'">
<img t-att-src="'/web/binary/image?model=ir.attachment&amp;field=datas&amp;id=' + str(attachment.id) + '&amp;resize=100,80'"
class='oe_attachment_embedded'></img>
</t>
<t t-if="attachment.file_type != 'webimage'">
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type + '.png'"
class='oe_attachment_webimage'></img>
</t>
<div class='oe_attachment_name'><t t-raw='attachment.name' /></div>
</a>
</div>
<div 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 '&amp;date_begin=%s' % date_begin or ''}#{date_end and '&amp;date_end=%s' % date_end or ''}"><t t-esc="group.name"/></a>
</li>
</ol>
</div>
<div class="row">
<h1 class="text-center">
<t t-esc="group.name"/> mailing list archives
</h1><h4 class="text-center text-muted">
<i class='fa fa-envelope-o'/>
<a t-attf-href="mailto:#{group.alias_id.alias_name}@#{group.alias_id.alias_domain}"><span t-field="group.alias_id"/></a>
</h4>
</div>
<div class="row">
<div class="col-md-3">
<h2>Archives</h2>
<ul class="nav nav-pills nav-stacked" id="group_mode">
<li t-attf-class="#{mode=='thread' and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=thread">By thread</a>
</li>
<li t-attf-class="#{mode=='date' and not date_begin and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=date">By date</a>
<ul class="nav nav-pills nav-stacked" style="margin-left: 8px;">
<t t-foreach="archives" t-as="month_archive">
<li t-att-class="month_archive['date_begin'] == date_begin and 'active' or None">
<a t-ignore="True" t-attf-href="/groups/#{ slug(group) }?mode=date&amp;date_begin=#{ month_archive['date_begin'] }&amp;date_end=#{month_archive['date_end']}">
<t t-esc="month_archive['date']"/>
<span class="pull-right badge" t-esc="month_archive['date_count']"/>
</a>
</li>
</t>
</ul>
</li>
</ul>
</div>
<div class="col-md-9">
<div>
<t t-call="website.pager"/>
</div>
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="messages"/>
<t t-set="msg_more_count" t-value="0"/>
<t t-set="thread_header" t-value="None"/>
</t>
<div>
<t t-call="website.pager"/>
</div>
</div>
</div>
</section>
</t>
</template>
<template id="group_message">
<t t-call="website.layout">
<t t-set="head">
<link rel='stylesheet' href="/website_mail_group/static/src/css/website_mail_group.css"/>
<script type="text/javascript" src="/website_mail_group/static/src/js/website_mail_group.js"/>
</t>
<section class="container">
<div class="row mt8">
<ol class="breadcrumb pull-left">
<li><a href="/groups">Mailing Lists</a></li>
<li>
<a t-attf-href="/groups/#{slug(group)}?#{mode and 'mode=%s' % mode or ''}#{date_begin and '&amp;date_begin=%s' % date_begin or ''}#{date_end and '&amp;date_end=%s' % date_end or ''}"><t t-esc="group.name"/></a>
</li>
<li t-if="message" class="active"><t t-esc="message.description"/></li>
</ol>
</div>
<div class="row">
<h1 class="text-center">
<t t-esc="group.name"/> mailing list archives
</h1><h4 class="text-center text-muted">
<i class='fa fa-envelope-o'/>
<a t-attf-href="mailto:#{group.alias_id.alias_name}@#{group.alias_id.alias_domain}"><span t-field="group.alias_id"/></a>
</h4>
</div>
<div class="row">
<div class="col-md-3">
<h4>Browse archives</h4>
<ul class="nav nav-pills nav-stacked" id="group_mode">
<li t-attf-class="#{mode=='thread' and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=thread">By thread</a>
</li>
<li t-attf-class="#{mode=='date' and not date_begin and 'active' or ''}">
<a t-attf-href="/groups/#{ slug(group) }?mode=date">By date</a>
<ul class="nav nav-pills nav-stacked" style="margin-left: 8px;">
<t t-foreach="archives" t-as="month_archive">
<li t-att-class="month_archive['date_begin'] == date_begin and 'active' or None">
<a t-ignore="True" t-attf-href="/groups/#{ slug(group) }?mode=date&amp;date_begin=#{ month_archive['date_begin'] }&amp;date_end=#{month_archive['date_end']}">
<t t-esc="month_archive['date']"/>
<span class="pull-right badge" t-esc="month_archive['date_count']"/>
</a>
</li>
</t>
</ul>
</li>
</ul>
</div>
<div class="col-md-9">
<div class="row">
<h4 class="col-md-6">
<t t-if="prev_message"><a t-attf-href='/groups/#{slug(group)}/#{slug(prev_message)}?#{mode and "mode=%s" % mode or ""}'>
<i class="fa fa-arrow-left"/> <t t-esc="prev_message.description"/>
</a></t>
</h4>
<h4 class="col-md-6">
<t t-if="next_message"><a class="pull-right" t-attf-href='/groups/#{slug(group)}/#{slug(next_message)}?#{mode and "mode=%s" % mode or ""}'>
<t t-esc="next_message.description"/> <i class="fa fa-arrow-right"/>
</a></t>
</h4>
</div>
<div class="media">
<img class="img-rounded pull-left mt0 media-object o_mg_avatar"
t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(message.id)"/>
<div class="media-body">
<h4 class="media-heading" t-esc="message.description"/>
<small>
by
<t t-if="message.author_id">
<span t-field="message.author_id" style="display: inline-block;" t-field-options='{
"widget": "contact",
"fields": ["name"]
}'/>
</t>
<t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
- <i class="fa fa-calendar"/> <t t-esc="message.date"/>
</small>
<div t-raw="message.body"/>
<div>
<p t-if="message.attachment_ids" class="mt8">
<a href="#" class="o_mg_link_hide">
<i class="fa fa-chevron-right"/> <t t-raw="len(message.attachment_ids)"/> attachments
</a>
<a href="#" class="o_mg_link_show">
<i class="fa fa-chevron-down"/> <t t-raw="len(message.attachment_ids)"/> attachments
</a>
</p>
<div class="o_mg_link_content">
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-att-href="'/mail/download_attachment?model=mail.message&amp;id='+str(message.id)+'&amp;method=download_attachment&amp;attachment_id='+str(attachment.id)" target="_blank">
<t t-if="attachment.file_type == 'webimage'">
<img t-att-src="'/web/binary/image?model=ir.attachment&amp;field=datas&amp;id=' + str(attachment.id) + '&amp;resize=100,80'"
class='oe_attachment_embedded'
t-att-title="attachment.name"/>
</t>
<t t-if="attachment.file_type != 'webimage'">
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type + '.png'"
class='oe_attachment_webimage'
t-att-title="attachment.name"/>
</t>
<div class='oe_attachment_name'><t t-raw='attachment.name' /></div>
</a>
</div>
</div>
</div>
</div>
</div>
<div t-if="message.child_ids" class="o_mg_replies">
<h4 class="page-header">Follow-Ups</h4>
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="message.child_ids[:replies_per_page]"/>
<t t-set="msg_more_count" t-value="len(message.child_ids) - replies_per_page"/>
<t t-set="thread_header" t-value="message"/>
</t>
</div>
<div t-if="message.parent_id">
<h4 class="page-header">Reference</h4>
<t t-call="website_mail_group.messages_short">
<t t-set="messages" t-value="[message.parent_id]"/>
</t>
</div>
</div>
</div>
</section>
</t>
</template>
<template id="messages_short">
<div>
<ul class="media-list">
<li t-foreach="messages" t-as="thread" class="media">
<img class="img-rounded pull-left mt0 media-object" style="height: 40px"
<img class="img-rounded pull-left mt0 media-object o_mg_avatar"
t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(thread.id)"/>
<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>

View File

@ -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

View File

@ -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, '')

View File

@ -5,7 +5,7 @@
<!-- Layout and common templates -->
<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_sale/static/src/js/website.tour.sale.js"></script>
</xpath>
</template>

View File

@ -41,6 +41,7 @@ import openerp.workflow
_logger = logging.getLogger(__name__)
class actions(osv.osv):
_name = 'ir.actions.actions'
_table = 'ir_actions'
@ -129,9 +130,15 @@ class ir_actions_report_xml(osv.osv):
Look up a report definition and render the report for the provided IDs.
"""
new_report = self._lookup_report(cr, name)
# in order to use current yml test files with qweb reports
if isinstance(new_report, (str, unicode)):
return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf'
if isinstance(new_report, (str, unicode)): # Qweb report
# The only case where a QWeb report is rendered with this method occurs when running
# yml tests originally written for RML reports.
if openerp.tools.config['test_enable'] and not tools.config['test_report_directory']:
# Only generate the pdf when a destination folder has been provided.
return self.pool['report'].get_html(cr, uid, res_ids, new_report, data=data, context=context), 'html'
else:
return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf'
else:
return new_report.create(cr, uid, res_ids, data, context)
@ -1182,7 +1189,4 @@ class ir_actions_act_client(osv.osv):
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -269,7 +269,10 @@ class WebRequest(object):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
be used as response."""
raise
self._failed = exception # prevent tx commit
if isinstance(exception, werkzeug.exceptions.HTTPException):
return exception
raise
def _call_function(self, *args, **kwargs):
request = self
@ -456,18 +459,20 @@ class JsonRequest(WebRequest):
def _handle_exception(self, exception):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
be used as response."""
_logger.exception("Exception during JSON request handling.")
self._failed = exception # prevent tx commit
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': serialize_exception(exception)
}
if isinstance(exception, AuthenticationError):
error['code'] = 100
error['message'] = "OpenERP Session Invalid"
return self._json_response(error=error)
be used as response."""
try:
return super(JsonRequest, self)._handle_exception(exception)
except Exception:
_logger.exception("Exception during JSON request handling.")
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': serialize_exception(exception)
}
if isinstance(exception, AuthenticationError):
error['code'] = 100
error['message'] = "OpenERP Session Invalid"
return self._json_response(error=error)
def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request

View File

@ -107,7 +107,7 @@ class NumberedCanvas(canvas.Canvas):
self.setFont("Helvetica", 8)
self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
" %(this)i / %(total)i" % {
'this': self._pageNumber+1,
'this': self._pageNumber,
'total': page_count,
}
)

View File

@ -87,8 +87,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re
if ('[[' in line) or ('[ [' in line):
_logger.error("Report %s may have bad expression near: \"%s\".", rname, line[80:])
# TODO more checks, what else can be a sign of a faulty report?
elif res_format == 'foobar':
# TODO
elif res_format == 'html':
pass
else:
_logger.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format)