[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/ /lib/
/man/ /man/
/share/ /share/
/src/ /src/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -378,7 +378,7 @@ class Website(openerp.addons.web.controllers.main.Home):
""" """
response = werkzeug.wrappers.Response() response = werkzeug.wrappers.Response()
return request.registry['website']._image( return request.registry['website']._image(
request.cr, request.uid, model, id, field, response) request.cr, request.uid, model, id, field, response, max_width, max_height)
#------------------------------------------------------ #------------------------------------------------------

View File

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

View File

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

View File

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

View File

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

View File

@ -61,8 +61,10 @@
t-att-data-editable="'1' if editable else None" t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None" t-att-data-translatable="'1' if translatable else None"
t-att-data-view-xmlid="xmlid if editable else None" t-att-data-view-xmlid="xmlid if editable else None"
t-att-data-main-object="repr(main_object) if editable else None"> t-att-data-main-object="repr(main_object) if editable else None"
t-att-data-oe-company-name="res_company.name">
<head> <head>
<meta charset="utf-8" />
<t t-if="main_object and 'website_meta_title' in main_object"> <t t-if="main_object and 'website_meta_title' in main_object">
<t t-set="title" t-value="main_object.website_meta_title"/> <t t-set="title" t-value="main_object.website_meta_title"/>
</t> </t>
@ -73,10 +75,9 @@
<t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t> <t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t>
</t> </t>
<meta name="viewport" content="initial-scale=1"/> <meta name="viewport" content="initial-scale=1"/>
<meta name="openerp.company" t-att-value="res_company.name"/> <meta name="description" t-att-content="main_object and 'website_meta_description' in main_object
<meta name="description" t-att-value="main_object and 'website_meta_description' in main_object
and main_object.website_meta_description or website_meta_description"/> and main_object.website_meta_description or website_meta_description"/>
<meta name="keywords" t-att-value="main_object and 'website_meta_keywords' in main_object <meta name="keywords" t-att-content="main_object and 'website_meta_keywords' in main_object
and main_object.website_meta_keywords or website_meta_keywords"/> and main_object.website_meta_keywords or website_meta_keywords"/>
<title><t t-esc="title"/></title> <title><t t-esc="title"/></title>
@ -137,14 +138,14 @@
<footer> <footer>
<div class="container hidden-print" id="footer_container"> <div class="container hidden-print" id="footer_container">
<div class="row"> <div class="row">
<div class="col-md-3" name="product"> <div class="col-md-3">
<h4>Our products &amp; Services</h4> <h4>Our products &amp; Services</h4>
<ul class="list-unstyled" name="products"> <ul class="list-unstyled" name="products">
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
</ul> </ul>
</div> </div>
<div class="col-md-3" name="info"> <div class="col-md-3" name="info">
<h4 name="info_title">Connect with us</h4> <h4>Connect with us</h4>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li><a href="/page/website.contactus">Contact us</a></li> <li><a href="/page/website.contactus">Contact us</a></li>
</ul> </ul>
@ -161,7 +162,7 @@
<a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a> <a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
</h2> </h2>
</div> </div>
<div class="col-md-5 col-lg-offset-1" name="about_us"> <div class="col-md-5 col-lg-offset-1">
<div> <div>
<h4> <h4>
<span t-field="res_company.name">Your Company</span> <span t-field="res_company.name">Your Company</span>
@ -306,7 +307,7 @@
<template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger &amp; Tests"> <template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger &amp; Tests">
<xpath expr='//t[@name="layout_head"]' position="after"> <xpath expr='//t[@name="layout_head"]' position="after">
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script> <t t-set="debugger_hook" t-value="1" />
</xpath> </xpath>
</template> </template>

View File

@ -86,8 +86,8 @@
</t> </t>
<div class="media"> <div class="media">
<a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}" <a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"
t-field="partner.image_small" t-field="partner.image"
t-field-options='{"widget": "image", "class": "media-object"}' t-field-options='{"widget": "image", "class": "media-object", "max_width": 128}'
></a> ></a>
<div class="media-body o_partner_body" style="min-height: 64px;"> <div class="media-body o_partner_body" style="min-height: 64px;">
<a class="media-heading" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"> <a class="media-heading" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}">

View File

@ -3,7 +3,7 @@
<data> <data>
<template id="debugger" inherit_id="website.debugger" name="Event Debugger"> <template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr="//script[last()]" position="after"> <xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script> <script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script>
</xpath> </xpath>
</template> </template>

View File

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

View File

@ -85,8 +85,8 @@
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }"> <a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
<span t-esc="track and track.name"/> <span t-esc="track and track.name"/>
</a> </a>
<div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker"> <div class="text-muted">
<small t-esc="speaker.display_name"/> <small t-esc="speakers[track.id]"/>
</div> </div>
</t> </t>
</td> </td>
@ -98,11 +98,11 @@
<t t-set="track" t-value="dt[1][False][0]"/> <t t-set="track" t-value="dt[1][False][0]"/>
<td t-att-colspan="len(locations)-1" t-attf-class="text-center event_color_#{track.color} #{track and 'event_track' or ''}"> <td t-att-colspan="len(locations)-1" t-attf-class="text-center event_color_#{track.color} #{track and 'event_track' or ''}">
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }"> <a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
<span t-esc="track.name"/><br/> <span t-esc="track.name"/>
<div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker">
<small t-esc="speaker.display_name"/>
</div>
</a> </a>
<div class="text-muted">
<small t-esc="speakers[track.id]"/>
</div>
</td> </td>
</t> </t>
</tr> </tr>

View File

@ -330,13 +330,12 @@ class WebsiteForum(http.Controller):
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
if kwargs.get('comment') and post.forum_id.id == forum.id: if kwargs.get('comment') and post.forum_id.id == forum.id:
# TDE FIXME: check that post_id is the question or one of its answers # TDE FIXME: check that post_id is the question or one of its answers
if request.registry['res.users'].has_group(cr, uid, 'website_mail.group_comment'): request.registry['forum.post'].message_post(
request.registry['forum.post'].message_post( cr, uid, post.id,
cr, uid, post.id, body=kwargs.get('comment'),
body=kwargs.get('comment'), type='comment',
type='comment', subtype='mt_comment',
subtype='mt_comment', context=dict(context, mail_create_nosubcribe=True))
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question))) return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True) @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,8 +35,8 @@ class table_compute(object):
index = 0 index = 0
maxy = 0 maxy = 0
for p in products: for p in products:
x = p.website_size_x x = min(max(p.website_size_x, 1), PPR)
y = p.website_size_y y = min(max(p.website_size_y, 1), PPR)
if index>PPG: if index>PPG:
x = y = 1 x = y = 1

View File

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

View File

@ -5,7 +5,7 @@
<!-- Layout and common templates --> <!-- Layout and common templates -->
<template id="debugger" inherit_id="website.debugger" name="Event Debugger"> <template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr="//script[last()]" position="after"> <xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_sale/static/src/js/website.tour.sale.js"></script> <script type="text/javascript" src="/website_sale/static/src/js/website.tour.sale.js"></script>
</xpath> </xpath>
</template> </template>

View File

@ -41,6 +41,7 @@ import openerp.workflow
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class actions(osv.osv): class actions(osv.osv):
_name = 'ir.actions.actions' _name = 'ir.actions.actions'
_table = 'ir_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. Look up a report definition and render the report for the provided IDs.
""" """
new_report = self._lookup_report(cr, name) new_report = self._lookup_report(cr, name)
# in order to use current yml test files with qweb reports
if isinstance(new_report, (str, unicode)): if isinstance(new_report, (str, unicode)): # Qweb report
return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf' # 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: else:
return new_report.create(cr, uid, res_ids, data, context) 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: # 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 """Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will to abitrary responses. Anything returned (except None) will
be used as response.""" 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): def _call_function(self, *args, **kwargs):
request = self request = self
@ -456,18 +459,20 @@ class JsonRequest(WebRequest):
def _handle_exception(self, exception): def _handle_exception(self, exception):
"""Called within an except block to allow converting exceptions """Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will to abitrary responses. Anything returned (except None) will
be used as response.""" be used as response."""
_logger.exception("Exception during JSON request handling.") try:
self._failed = exception # prevent tx commit return super(JsonRequest, self)._handle_exception(exception)
error = { except Exception:
'code': 200, _logger.exception("Exception during JSON request handling.")
'message': "OpenERP Server Error", error = {
'data': serialize_exception(exception) 'code': 200,
} 'message': "OpenERP Server Error",
if isinstance(exception, AuthenticationError): 'data': serialize_exception(exception)
error['code'] = 100 }
error['message'] = "OpenERP Session Invalid" if isinstance(exception, AuthenticationError):
return self._json_response(error=error) error['code'] = 100
error['message'] = "OpenERP Session Invalid"
return self._json_response(error=error)
def dispatch(self): def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request """ 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.setFont("Helvetica", 8)
self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40), self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
" %(this)i / %(total)i" % { " %(this)i / %(total)i" % {
'this': self._pageNumber+1, 'this': self._pageNumber,
'total': page_count, '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): if ('[[' in line) or ('[ [' in line):
_logger.error("Report %s may have bad expression near: \"%s\".", rname, line[80:]) _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? # TODO more checks, what else can be a sign of a faulty report?
elif res_format == 'foobar': elif res_format == 'html':
# TODO
pass pass
else: else:
_logger.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format) _logger.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format)