summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Dony <odo@openerp.com>2014-05-27 20:49:49 +0200
committerOlivier Dony <odo@openerp.com>2014-05-27 20:49:49 +0200
commit04211015fc3d4be2a8c65b9d977c2d9c4dac8be7 (patch)
treeb651a2900e630ed2ff780ddb960b55470f3bd118
parente16233217c6919b33e6d1135ff07538d839f6a2f (diff)
parent045285123f2551ca74753260fcf807af4bc413d0 (diff)
[MERGE] Forward-port of latest saas-4 fixes, up to 0452851
-rw-r--r--.gitignore2
-rw-r--r--addons/account/account_invoice_view.xml2
-rw-r--r--addons/account/report/account_aged_partner_balance.py26
-rw-r--r--addons/account/views/report_invoice.xml2
-rw-r--r--addons/account/views/report_overdue.xml2
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis.py9
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis_view.xml2
-rw-r--r--addons/account_budget/account_budget.py3
-rw-r--r--addons/account_followup/views/report_followup.xml2
-rw-r--r--addons/base_gengo/doc/changelog.rst4
-rw-r--r--addons/crm_partner_assign/security/ir.model.access.csv3
-rw-r--r--addons/delivery/delivery.py9
-rw-r--r--addons/hr_payroll/views/report_payslip.xml2
-rw-r--r--addons/hr_recruitment/hr_recruitment.py11
-rw-r--r--addons/lunch/views/report_lunchorder.xml2
-rw-r--r--addons/mail/mail_thread.py7
-rw-r--r--addons/mail/static/src/js/mail.js30
-rw-r--r--addons/mail/static/src/xml/mail.xml4
-rw-r--r--addons/mass_mailing/models/mass_mailing.py11
-rw-r--r--addons/mrp_repair/views/report_mrprepairorder.xml2
-rw-r--r--addons/pad/static/src/img/pad_link_companies.jpegbin0 -> 70544 bytes
-rw-r--r--addons/purchase/edi/purchase_order_action_data.xml2
-rw-r--r--addons/purchase/purchase.py2
-rw-r--r--addons/purchase/views/report_purchaseorder.xml2
-rw-r--r--addons/purchase/views/report_purchasequotation.xml2
-rw-r--r--addons/report/controllers/main.py2
-rw-r--r--addons/report/data/report_paperformat.xml2
-rw-r--r--addons/report/models/report.py74
-rw-r--r--addons/report/static/src/js/qwebactionmanager.js52
-rw-r--r--addons/report/views/layouts.xml4
-rw-r--r--addons/report_intrastat/views/report_intrastatinvoice.xml2
-rw-r--r--addons/report_webkit/webkit_report.py40
-rw-r--r--addons/sale/views/report_saleorder.xml2
-rw-r--r--addons/survey/security/ir.model.access.csv14
-rw-r--r--addons/web/static/src/js/view_form.js12
-rw-r--r--addons/web/static/src/js/views.js10
-rw-r--r--addons/website/controllers/main.py2
-rw-r--r--addons/website/models/ir_http.py85
-rw-r--r--addons/website/models/ir_qweb.py16
-rw-r--r--addons/website/models/website.py5
-rw-r--r--addons/website/static/src/js/website.seo.js10
-rw-r--r--addons/website/views/website_templates.xml17
-rw-r--r--addons/website_crm_partner_assign/views/website_crm_partner_assign.xml4
-rw-r--r--addons/website_event_sale/views/website_event_sale.xml2
-rw-r--r--addons/website_event_track/controllers/event.py11
-rw-r--r--addons/website_event_track/views/website_event.xml12
-rw-r--r--addons/website_forum/controllers/main.py13
-rw-r--r--addons/website_hr_recruitment/security/website_hr_recruitment_security.xml20
-rw-r--r--addons/website_mail/controllers/main.py30
-rw-r--r--addons/website_mail/models/mail_message.py2
-rw-r--r--addons/website_mail/static/src/js/website_mail.js42
-rw-r--r--addons/website_mail/views/snippets.xml27
-rw-r--r--addons/website_mail_group/controllers/main.py46
-rw-r--r--addons/website_mail_group/static/src/css/website_mail_group.css12
-rw-r--r--addons/website_mail_group/static/src/js/website_mail_group.js42
-rw-r--r--addons/website_mail_group/views/website_mail_group.xml378
-rw-r--r--addons/website_sale/controllers/main.py4
-rw-r--r--addons/website_sale/models/product.py1
-rw-r--r--addons/website_sale/views/templates.xml2
-rw-r--r--openerp/addons/base/ir/ir_actions.py16
-rw-r--r--openerp/http.py31
-rw-r--r--openerp/report/render/rml2pdf/trml2pdf.py2
-rw-r--r--openerp/tools/test_reports.py3
63 files changed, 773 insertions, 419 deletions
diff --git a/.gitignore b/.gitignore
index 6148a29a95d..4b38dc02ac7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,4 @@ install/win32/meta.py
/lib/
/man/
/share/
-/src/
+/src/ \ No newline at end of file
diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml
index 61c397364b9..fe701a85661 100644
--- a/addons/account/account_invoice_view.xml
+++ b/addons/account/account_invoice_view.xml
@@ -60,7 +60,7 @@
</group>
<group>
<field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '&lt;&gt;', 'view')]" name="account_id" on_change="onchange_account_id(product_id, parent.partner_id, parent.type, parent.fiscal_position,account_id)" groups="account.group_account_user"/>
- <field name="invoice_line_tax_id" context="{'type':parent.type}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/>
+ <field name="invoice_line_tax_id" context="{'type':parent.get('type')}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/>
<field domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id)]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
</group>
diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py
index 3fd83c19207..2e3c9b7c374 100644
--- a/addons/account/report/account_aged_partner_balance.py
+++ b/addons/account/report/account_aged_partner_balance.py
@@ -161,7 +161,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
dates_query += ' < %s)'
args_list += (form[str(i)]['stop'],)
args_list += (self.date_from,)
- self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit)
+ self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit), l.reconcile_partial_id
FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id=am.id)
AND (am.state IN %s)
@@ -173,12 +173,24 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
AND account_account.active
AND ''' + dates_query + '''
AND (l.date <= %s)
- GROUP BY l.partner_id''', args_list)
- t = self.cr.fetchall()
- d = {}
- for i in t:
- d[i[0]] = i[1]
- history.append(d)
+ GROUP BY l.partner_id, l.reconcile_partial_id''', args_list)
+ partners_partial = self.cr.fetchall()
+ partners_amount = dict((i[0],0) for i in partners_partial)
+ for partner_info in partners_partial:
+ if partner_info[2]:
+ # in case of partial reconciliation, we want to keep the left amount in the oldest period
+ self.cr.execute('''SELECT MIN(COALESCE(date_maturity,date)) FROM account_move_line WHERE reconcile_partial_id = %s''', (partner_info[2],))
+ date = self.cr.fetchall()
+ if date and args_list[-3] <= date[0][0] <= args_list[-2]:
+ # partial reconcilation
+ self.cr.execute('''SELECT SUM(l.debit-l.credit)
+ FROM account_move_line AS l
+ WHERE l.reconcile_partial_id = %s''', (partner_info[2],))
+ unreconciled_amount = self.cr.fetchall()
+ partners_amount[partner_info[0]] += unreconciled_amount[0][0]
+ else:
+ partners_amount[partner_info[0]] += partner_info[1]
+ history.append(partners_amount)
for partner in partners:
values = {}
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index 6c4b701c6cf..a250eb9ebf7 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -5,7 +5,7 @@
<t t-call="report.external_layout">
<div class="page">
<div class="row">
- <div class="col-xs-4 col-xs-offset-8">
+ <div class="col-xs-5 col-xs-offset-7">
<address t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
<span t-field="o.partner_id.vat"/>
diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml
index bc522aa8b9d..df3934ff0de 100644
--- a/addons/account/views/report_overdue.xml
+++ b/addons/account/views/report_overdue.xml
@@ -5,7 +5,7 @@
<t t-call="report.external_layout">
<div class="page">
<div class="row">
- <div class="col-xs-4 col-xs-offset-6">
+ <div class="col-xs-5 col-xs-offset-7">
<span t-field="o.name"/><br/>
<span t-raw="addresses[o.id].replace('\n\n', '\n').replace('\n', '&lt;br&gt;')"/>
<span t-field="o.vat"/>
diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py
index 8d470f45637..4b2352bc880 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis.py
+++ b/addons/account_analytic_analysis/account_analytic_analysis.py
@@ -69,7 +69,7 @@ class account_analytic_invoice_line(osv.osv):
if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=local_context)
if part.lang:
- context.update({'lang': part.lang})
+ local_context.update({'lang': part.lang})
result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=local_context)
@@ -79,7 +79,12 @@ class account_analytic_invoice_line(osv.osv):
price = res.price
else:
price = res.list_price
- result.update({'name': name or res.description or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price})
+ if not name:
+ name = self.pool.get('product.product').name_get(cr, uid, [res.id], context=local_context)[0][1]
+ if res.description_sale:
+ name += '\n'+res.description_sale
+
+ result.update({'name': name or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price})
res_final = {'value':result}
if result['uom_id'] != res.uom_id.id:
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
index 97fb15e9290..af6eb47ffa4 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml
+++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
@@ -169,7 +169,7 @@
<div attrs="{'invisible': [('recurring_invoices','=',False)]}">
<field name="recurring_invoice_line_ids">
<tree string="Account Analytic Lines" editable="bottom">
- <field name="product_id" on_change="product_id_change(product_id, uom_id, quantity, name, parent.partner_id, price_unit, parent.pricelist_id, parent.company_id)"/>
+ <field name="product_id" on_change="product_id_change(product_id, uom_id, quantity, False, parent.partner_id, False, parent.pricelist_id, parent.company_id)"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id"/>
diff --git a/addons/account_budget/account_budget.py b/addons/account_budget/account_budget.py
index 753a5df79f4..78e85716bbd 100644
--- a/addons/account_budget/account_budget.py
+++ b/addons/account_budget/account_budget.py
@@ -22,6 +22,7 @@
import datetime
from openerp.osv import fields, osv
+from openerp.tools import ustr
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
@@ -114,7 +115,7 @@ class crossovered_budget_lines(osv.osv):
for line in self.browse(cr, uid, ids, context=context):
acc_ids = [x.id for x in line.general_budget_id.account_ids]
if not acc_ids:
- raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % str(line.general_budget_id.name))
+ raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % ustr(line.general_budget_id.name))
date_to = line.date_to
date_from = line.date_from
if context.has_key('wizard_date_from'):
diff --git a/addons/account_followup/views/report_followup.xml b/addons/account_followup/views/report_followup.xml
index 6c79fe4cc55..c0d98e7bb28 100644
--- a/addons/account_followup/views/report_followup.xml
+++ b/addons/account_followup/views/report_followup.xml
@@ -7,7 +7,7 @@
<t t-call="report.external_layout">
<div class="page">
<div class="row">
- <div class="col-xs-4 col-xs-offset-6">
+ <div class="col-xs-5 col-xs-offset-7">
<div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
<span t-field="o.partner_id.vat"/>
diff --git a/addons/base_gengo/doc/changelog.rst b/addons/base_gengo/doc/changelog.rst
index fe343069a38..19f93bfa5a8 100644
--- a/addons/base_gengo/doc/changelog.rst
+++ b/addons/base_gengo/doc/changelog.rst
@@ -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).
diff --git a/addons/crm_partner_assign/security/ir.model.access.csv b/addons/crm_partner_assign/security/ir.model.access.csv
index 6a26c574a1c..d5324ba6c53 100644
--- a/addons/crm_partner_assign/security/ir.model.access.csv
+++ b/addons/crm_partner_assign/security/ir.model.access.csv
@@ -3,6 +3,7 @@ access_ crm_lead_report_assign,crm.lead.report.assign,model_crm_lead_report_assi
access_ crm_lead_report_assign_all,crm.lead.report.assign.all,model_crm_lead_report_assign,base.group_user,1,0,0,0
access_crm_partner_report,crm.partner.report.assign.all,model_crm_partner_report_assign,base.group_sale_salesman,1,0,0,0
access_res_partner_grade,res.partner.grade,model_res_partner_grade,base.group_sale_salesman,1,1,1,0
+access_res_partner_grade_public,res.partner.grade,model_res_partner_grade,base.group_public,1,0,0,0
access_res_partner_grade_manager,res.partner.grade.manager,model_res_partner_grade,base.group_sale_manager,1,1,1,1
"access_partner_activation_manager","res.partner.activation.manager","model_res_partner_activation","base.group_partner_manager",1,1,1,1
-partner_access_crm_lead,crm.lead,model_crm_lead,base.group_portal,1,1,0,0 \ No newline at end of file
+partner_access_crm_lead,crm.lead,model_crm_lead,base.group_portal,1,1,0,0
diff --git a/addons/delivery/delivery.py b/addons/delivery/delivery.py
index 46d76180d08..873b7e60662 100644
--- a/addons/delivery/delivery.py
+++ b/addons/delivery/delivery.py
@@ -192,15 +192,16 @@ class delivery_grid(osv.osv):
weight = 0
volume = 0
quantity = 0
+ product_uom_obj = self.pool.get('product.uom')
for line in order.order_line:
if not line.product_id or line.is_delivery:
continue
- weight += (line.product_id.weight or 0.0) * line.product_uom_qty
- volume += (line.product_id.volume or 0.0) * line.product_uom_qty
- quantity += line.product_uom_qty
+ q = product_uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_uos_qty, line.product_id.uom_id.id)
+ weight += (line.product_id.weight or 0.0) * q
+ volume += (line.product_id.volume or 0.0) * q
+ quantity += q
total = order.amount_total or 0.0
-
return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context)
def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None):
diff --git a/addons/hr_payroll/views/report_payslip.xml b/addons/hr_payroll/views/report_payslip.xml
index 5ac9dfa557e..b30cb089d1a 100644
--- a/addons/hr_payroll/views/report_payslip.xml
+++ b/addons/hr_payroll/views/report_payslip.xml
@@ -19,7 +19,7 @@
<tr>
<td><strong>Address</strong></td>
<td colspan="3">
- <div t-filed="o.employee_id.address_home_id"
+ <div t-field="o.employee_id.address_home_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
</td>
</tr>
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index 9c69f5db94b..775b84756ce 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -114,6 +114,15 @@ class hr_applicant(osv.Model):
return int(department_ids[0][0])
return None
+ def _get_default_company_id(self, cr, uid, department_id=None, context=None):
+ company_id = False
+ if department_id:
+ department = self.pool['hr.department'].browse(cr, uid, department_id, context=context)
+ company_id = department.company_id.id if department and department.company_id else False
+ if not company_id:
+ company_id = self.pool['res.company']._company_default_get(cr, uid, 'hr.applicant', context=context)
+ return company_id
+
def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
access_rights_uid = access_rights_uid or uid
stage_obj = self.pool.get('hr.recruitment.stage')
@@ -231,7 +240,7 @@ class hr_applicant(osv.Model):
'user_id': lambda s, cr, uid, c: uid,
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c),
+ 'company_id': lambda s, cr, uid, c: s._get_default_company_id(cr, uid, s._get_default_department_id(cr, uid, c), c),
'color': 0,
'date_last_stage_update': fields.datetime.now,
}
diff --git a/addons/lunch/views/report_lunchorder.xml b/addons/lunch/views/report_lunchorder.xml
index 93d43f6521d..30401bc142b 100644
--- a/addons/lunch/views/report_lunchorder.xml
+++ b/addons/lunch/views/report_lunchorder.xml
@@ -8,7 +8,7 @@
<div class="oe_structure"/>
<div class="row">
- <div class="col-xs-4 col-xs-offset-7">
+ <div class="col-xs-5 col-xs-offset-7">
<div t-field="user.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
</div>
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index acb1793b7bd..11f93e25632 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -669,9 +669,10 @@ class mail_thread(osv.AbstractModel):
def message_get_default_recipients(self, cr, uid, ids, context=None):
if context and context.get('thread_model') and context['thread_model'] in self.pool and context['thread_model'] != self._name:
- sub_ctx = dict(context)
- sub_ctx.pop('thread_model')
- return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
+ if hasattr(self.pool[context['thread_model']], 'message_get_default_recipients'):
+ sub_ctx = dict(context)
+ sub_ctx.pop('thread_model')
+ return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
res = {}
for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
recipient_ids, email_to, email_cc = set(), False, False
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 99a7751e15e..e5eca724659 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -919,15 +919,45 @@ openerp.mail = function (session) {
this.$('.oe_mail_expand').on('click', this.on_expand);
this.$('.oe_mail_reduce').on('click', this.on_expand);
this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
+ this.$('.oe_mail_action_author').on('click', this.on_record_author_clicked);
},
on_record_clicked: function (event) {
+ event.preventDefault();
+ var self = this;
var state = {
'model': this.model,
'id': this.res_id,
'title': this.record_name
};
session.webclient.action_manager.do_push_state(state);
+ this.context.params = {
+ model: this.model,
+ res_id: this.res_id,
+ };
+ this.thread.ds_thread.call("message_redirect_action", {context: this.context}).then(function(action){
+ self.do_action(action);
+ });
+ },
+
+ on_record_author_clicked: function (event) {
+ event.preventDefault();
+ var partner_id = $(event.target).data('partner');
+ var state = {
+ 'model': 'res.partner',
+ 'id': partner_id,
+ 'title': this.record_name
+ };
+ session.webclient.action_manager.do_push_state(state);
+ var action = {
+ type:'ir.actions.act_window',
+ view_type: 'form',
+ view_mode: 'form',
+ res_model: 'res.partner',
+ views: [[false, 'form']],
+ res_id: partner_id,
+ }
+ this.do_action(action);
},
/* Call the on_compose_message on the thread of this message. */
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index ec7b0fa2911..3aea4a19555 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -273,7 +273,7 @@
<t t-if="widget.attachment_ids.length > 0">
<div class="oe_msg_attachment_list"></div>
</t>
- <a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-esc="widget.author_id[2]"/></a>
+ <a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-data-partner="widget.author_id[0]" class="oe_mail_action_author"><t t-esc="widget.author_id[2]"/></a>
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-esc="widget.author_id[2]"/></span>
<t t-if="widget.type == 'notification'">
updated document
@@ -293,7 +293,7 @@
<t t-if="widget.type == 'notification' or ( (widget.type == 'email' or widget.type == 'comment') and (widget.subtype or widget.partner_ids.length > 0))"
t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
<span t-attf-class="oe_partner_follower">
- <a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-esc="partner[1]"/></a>
+ <a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}" t-att-data-partner="partner[0]" class="oe_mail_action_author"><t t-esc="partner[1]"/></a>
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
</span>
<t t-if="!partner_last">,</t>
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 2e8a82a8f4d..f721ed2f385 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -4,10 +4,9 @@ from datetime import datetime
from dateutil import relativedelta
import json
import random
-import urllib
-import urlparse
from openerp import tools
+from openerp.exceptions import Warning
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
from openerp.osv import osv, fields
@@ -62,6 +61,12 @@ class MassMailingContact(osv.Model):
rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
return self.name_get(cr, uid, [rec_id], context)[0]
+ def message_get_default_recipients(self, cr, uid, ids, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = {'partner_ids': [], 'email_to': record.email, 'email_cc': False}
+ return res
+
class MassMailingList(osv.Model):
"""Model of a contact list. """
@@ -549,6 +554,8 @@ class MassMailing(osv.Model):
for mailing in self.browse(cr, uid, ids, context=context):
# instantiate an email composer + send emails
res_ids = self.get_recipients(cr, uid, mailing, context=context)
+ if not res_ids:
+ raise Warning('Please select recipients.')
comp_ctx = dict(context, active_ids=res_ids)
composer_values = {
'author_id': author_id,
diff --git a/addons/mrp_repair/views/report_mrprepairorder.xml b/addons/mrp_repair/views/report_mrprepairorder.xml
index 0ea3f22167c..2897fee1fd9 100644
--- a/addons/mrp_repair/views/report_mrprepairorder.xml
+++ b/addons/mrp_repair/views/report_mrprepairorder.xml
@@ -22,7 +22,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
- <div class="col-xs-4 col-xs-offset-2">
+ <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
</div>
diff --git a/addons/pad/static/src/img/pad_link_companies.jpeg b/addons/pad/static/src/img/pad_link_companies.jpeg
new file mode 100644
index 00000000000..1bce3a56186
--- /dev/null
+++ b/addons/pad/static/src/img/pad_link_companies.jpeg
Binary files differ
diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml
index fc195f86ee4..391c61a8e94 100644
--- a/addons/purchase/edi/purchase_order_action_data.xml
+++ b/addons/purchase/edi/purchase_order_action_data.xml
@@ -111,7 +111,7 @@
<p style="border-left: 1px solid #8e0000; margin-left: 30px;">
&nbsp;&nbsp;<strong>REFERENCES</strong><br />
&nbsp;&nbsp;Order number: <strong>${object.name}</strong><br />
- &nbsp;&nbsp;Order total: <strong>${object.amount_total} ${object.pricelist_id.currency_id.name}</strong><br />
+ &nbsp;&nbsp;Order total: <strong>${object.amount_total} ${object.currency_id.name}</strong><br />
&nbsp;&nbsp;Order date: ${object.date_order}<br />
% if object.origin:
&nbsp;&nbsp;Order reference: ${object.origin}<br />
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 6ff6feae376..67b07644dc4 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -220,7 +220,7 @@ class purchase_order(osv.osv):
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
- 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
+ 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been validated"),
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True,
readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
diff --git a/addons/purchase/views/report_purchaseorder.xml b/addons/purchase/views/report_purchaseorder.xml
index 300149e4f1d..91486c035aa 100644
--- a/addons/purchase/views/report_purchaseorder.xml
+++ b/addons/purchase/views/report_purchaseorder.xml
@@ -23,7 +23,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
- <div class="col-xs-4 col-xs-offset-2">
+ <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
</div>
diff --git a/addons/purchase/views/report_purchasequotation.xml b/addons/purchase/views/report_purchasequotation.xml
index a18619f99f9..6abd64b2f33 100644
--- a/addons/purchase/views/report_purchasequotation.xml
+++ b/addons/purchase/views/report_purchasequotation.xml
@@ -23,7 +23,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
- <div class="col-xs-4 col-xs-offset-2">
+ <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
</div>
diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py
index de3bbd63db6..c6bc13d38f8 100644
--- a/addons/report/controllers/main.py
+++ b/addons/report/controllers/main.py
@@ -66,7 +66,7 @@ class ReportController(Controller):
# Misc. route utils
#------------------------------------------------------
@route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user")
- def report_barcode(self, type, value, width=300, height=50):
+ def report_barcode(self, type, value, width=600, height=100):
"""Contoller able to render barcode images thanks to reportlab.
Samples:
<img t-att-src="'/report/barcode/QR/%s' % o.name"/>
diff --git a/addons/report/data/report_paperformat.xml b/addons/report/data/report_paperformat.xml
index 275d65edd13..f05e0afb6f0 100644
--- a/addons/report/data/report_paperformat.xml
+++ b/addons/report/data/report_paperformat.xml
@@ -9,7 +9,7 @@
<field name="page_width">0</field>
<field name="orientation">Portrait</field>
<field name="margin_top">40</field>
- <field name="margin_bottom">20</field>
+ <field name="margin_bottom">23</field>
<field name="margin_left">7</field>
<field name="margin_right">7</field>
<field name="header_line" eval="False" />
diff --git a/addons/report/models/report.py b/addons/report/models/report.py
index d8a51387cb7..cbbee7fcebd 100644
--- a/addons/report/models/report.py
+++ b/addons/report/models/report.py
@@ -25,10 +25,8 @@ from openerp.tools.translate import _
from openerp.addons.web.http import request
from openerp.tools.safe_eval import safe_eval as eval
-import os
+import re
import time
-import psutil
-import signal
import base64
import logging
import tempfile
@@ -52,15 +50,19 @@ try:
['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
except OSError:
- _logger.error('You need wkhtmltopdf to print a pdf version of the reports.')
+ _logger.info('You need wkhtmltopdf to print a pdf version of the reports.')
else:
out, err = process.communicate()
- version = out.splitlines()[1].strip()
- version = version.split(' ')[1]
+ version = re.search('([0-9.]+)', out).group(0)
if LooseVersion(version) < LooseVersion('0.12.0'):
- _logger.warning('Upgrade wkhtmltopdf to (at least) 0.12.0')
+ _logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0')
wkhtmltopdf_state = 'upgrade'
- wkhtmltopdf_state = 'ok'
+ else:
+ wkhtmltopdf_state = 'ok'
+
+ if config['workers'] == 1:
+ _logger.info('You need to start OpenERP with at least two workers to print a pdf version of the reports.')
+ wkhtmltopdf_state = 'workers'
class Report(osv.Model):
@@ -343,19 +345,20 @@ class Report(osv.Model):
command_args = []
tmp_dir = tempfile.gettempdir()
- # Passing the cookie to wkhtmltopdf in order to resolve URL.
+ # Passing the cookie to wkhtmltopdf in order to resolve internal links.
try:
if request:
command_args.extend(['--cookie', 'session_id', request.session.sid])
except AttributeError:
pass
- # Display arguments
+ # Wkhtmltopdf arguments
+ command_args.extend(['--quiet']) # Less verbose error messages
if paperformat:
+ # Convert the paperformat record into arguments
command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args))
- command_args.extend(['--load-error-handling', 'ignore'])
-
+ # Force the landscape orientation if necessary
if landscape and '--orientation' in command_args:
command_args_copy = list(command_args)
for index, elem in enumerate(command_args_copy):
@@ -366,61 +369,46 @@ class Report(osv.Model):
elif landscape and not '--orientation' in command_args:
command_args.extend(['--orientation', 'landscape'])
+ # Execute WKhtmltopdf
pdfdocuments = []
- # HTML to PDF thanks to WKhtmltopdf
for index, reporthtml in enumerate(bodies):
- command_arg_local = []
- pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.',
- mode='w+b')
- # Directly load the document if we have it
+ local_command_args = []
+ pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', mode='w+b')
+
+ # Directly load the document if we already have it
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
pdfreport.seek(0)
pdfdocuments.append(pdfreport)
continue
- # Header stuff
+ # Wkhtmltopdf handles header/footer as separate pages. Create them if necessary.
if headers:
- head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.',
- dir=tmp_dir, mode='w+')
+ head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+')
head_file.write(headers[index])
head_file.seek(0)
- command_arg_local.extend(['--header-html', head_file.name])
-
- # Footer stuff
+ local_command_args.extend(['--header-html', head_file.name])
if footers:
- foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.',
- dir=tmp_dir, mode='w+')
+ foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+')
foot_file.write(footers[index])
foot_file.seek(0)
- command_arg_local.extend(['--footer-html', foot_file.name])
+ local_command_args.extend(['--footer-html', foot_file.name])
# Body stuff
- content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.',
- dir=tmp_dir, mode='w+')
+ content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+')
content_file.write(reporthtml[1])
content_file.seek(0)
try:
- # If the server is running with only one worker, ask to create a secund to be able
- # to serve the http request of wkhtmltopdf subprocess.
- if config['workers'] == 1:
- ppid = psutil.Process(os.getpid()).ppid
- os.kill(ppid, signal.SIGTTIN)
-
- wkhtmltopdf = command + command_args + command_arg_local
+ wkhtmltopdf = command + command_args + local_command_args
wkhtmltopdf += [content_file.name] + [pdfreport.name]
- process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = process.communicate()
- if config['workers'] == 1:
- os.kill(ppid, signal.SIGTTOU)
-
- if process.returncode != 0:
+ if process.returncode not in [0, 1]:
raise osv.except_osv(_('Report (PDF)'),
- _('wkhtmltopdf failed with error code = %s. '
+ _('Wkhtmltopdf failed (error code: %s). '
'Message: %s') % (str(process.returncode), err))
# Save the pdf in attachment if marked
@@ -446,7 +434,7 @@ class Report(osv.Model):
except:
raise
- # Get and return the full pdf
+ # Return the entire document
if len(pdfdocuments) == 1:
content = pdfdocuments[0].read()
pdfdocuments[0].close()
diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js
index 37a0fae6ea6..cb0f3921482 100644
--- a/addons/report/static/src/js/qwebactionmanager.js
+++ b/addons/report/static/src/js/qwebactionmanager.js
@@ -54,33 +54,35 @@ openerp.report = function(instance) {
if (action.report_type == 'qweb-html') {
window.open(report_url, '_blank', 'height=900,width=1280');
instance.web.unblockUI();
- } else {
+ } else if (action.report_type === 'qweb-pdf') {
// Trigger the download of the pdf/controller report
-
- if (action.report_type == 'qweb-pdf') {
- (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
- // Fallback of qweb-pdf if wkhtmltopdf is not installed
- if (presence == 'install' && action.report_type == 'qweb-pdf') {
- self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
- system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\
- wkhtmltopdf.org</a>'), true);
- report_url = report_url.substring(12)
- window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
- instance.web.unblockUI();
- return;
- } else {
- if (presence == 'upgrade') {
- self.do_notify(_t('Report'), _t('You should upgrade your version of\
- Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
- support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
- target="_blank">wkhtmltopdf.org</a>'), true);
- }
- }
- return trigger_download(self.session, response, c);
- });
- } else if (action.report_type == 'controller') {
+ (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
+ // Fallback on html if wkhtmltopdf is not installed or if OpenERP is started with one worker
+ if (presence === 'install') {
+ self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
+system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\
+wkhtmltopdf.org</a>'), true);
+ report_url = report_url.substring(12)
+ window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
+ instance.web.unblockUI();
+ return;
+ } else if (presence === 'workers') {
+ self.do_notify(_t('Report'), _t('You need to start OpenERP with at least two \
+workers to print a pdf version of the reports.'), true);
+ report_url = report_url.substring(12)
+ window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
+ instance.web.unblockUI();
+ return;
+ } else if (presence === 'upgrade') {
+ self.do_notify(_t('Report'), _t('You should upgrade your version of\
+ Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
+ support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
+ target="_blank">wkhtmltopdf.org</a>'), true);
+ }
return trigger_download(self.session, response, c);
- }
+ });
+ } else if (action.report_type === 'controller') {
+ return trigger_download(self.session, response, c);
}
} else {
return self._super(action, options);
diff --git a/addons/report/views/layouts.xml b/addons/report/views/layouts.xml
index dd7a404196e..f9c0ea7ef73 100644
--- a/addons/report/views/layouts.xml
+++ b/addons/report/views/layouts.xml
@@ -9,7 +9,8 @@
t-att-data-main-object="repr(main_object) if editable else None"
t-att-data-report-margin-top="data_report_margin_top if data_report_margin_top else None"
t-att-data-report-header-spacing="data_report_header_spacing if data_report_header_spacing else None"
- t-att-data-report-dpi="data_report_dpi if data_report_dpi else None">
+ t-att-data-report-dpi="data_report_dpi if data_report_dpi else None"
+ t-att-data-oe-company-name="res_company.name">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
@@ -19,7 +20,6 @@
<t t-if="not title and main_object and 'name' in main_object">
<t t-set="additional_title" t-value="main_object.name"/>
</t>
- <meta name="openerp.company" t-att-value="res_company.name"/>
<meta name="description" t-att-value="main_object and 'website_meta_description' in main_object
and main_object.website_meta_description or website_meta_description"/>
<meta name="keywords" t-att-value="main_object and 'website_meta_keywords' in main_object
diff --git a/addons/report_intrastat/views/report_intrastatinvoice.xml b/addons/report_intrastat/views/report_intrastatinvoice.xml
index c534f808c95..abf4bdd56ba 100644
--- a/addons/report_intrastat/views/report_intrastatinvoice.xml
+++ b/addons/report_intrastat/views/report_intrastatinvoice.xml
@@ -5,7 +5,7 @@
<t t-call="report.external_layout">
<div class="page">
<div class="row">
- <div class="col-xs-4 col-xs-offset-8">
+ <div class="col-xs-5 col-xs-offset-7">
<address t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
<span t-field="o.partner_id.vat"/>
diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py
index 60fb536ca96..c442ea32549 100644
--- a/addons/report_webkit/webkit_report.py
+++ b/addons/report_webkit/webkit_report.py
@@ -156,8 +156,8 @@ class WebKitParser(report_sxw):
"""Call webkit in order to generate pdf"""
if not webkit_header:
webkit_header = report_xml.webkit_header
- tmp_dir = tempfile.gettempdir()
- out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.")
+ fd, out_filename = tempfile.mkstemp(suffix=".pdf",
+ prefix="webkit.tmp.")
file_to_del = [out_filename]
if comm_path:
command = [comm_path]
@@ -168,25 +168,15 @@ class WebKitParser(report_sxw):
# default to UTF-8 encoding. Use <meta charset="latin-1"> to override.
command.extend(['--encoding', 'utf-8'])
if header :
- head_file = file( os.path.join(
- tmp_dir,
- str(time.time()) + '.head.html'
- ),
- 'w'
- )
- head_file.write(self._sanitize_html(header.encode('utf-8')))
- head_file.close()
+ with tempfile.NamedTemporaryFile(suffix=".head.html",
+ delete=False) as head_file:
+ head_file.write(self._sanitize_html(header.encode('utf-8')))
file_to_del.append(head_file.name)
command.extend(['--header-html', head_file.name])
if footer :
- foot_file = file( os.path.join(
- tmp_dir,
- str(time.time()) + '.foot.html'
- ),
- 'w'
- )
- foot_file.write(self._sanitize_html(footer.encode('utf-8')))
- foot_file.close()
+ with tempfile.NamedTemporaryFile(suffix=".foot.html",
+ delete=False) as foot_file:
+ foot_file.write(self._sanitize_html(footer.encode('utf-8')))
file_to_del.append(foot_file.name)
command.extend(['--footer-html', foot_file.name])
@@ -204,10 +194,10 @@ class WebKitParser(report_sxw):
command.extend(['--page-size', str(webkit_header.format).replace(',', '.')])
count = 0
for html in html_list :
- html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
- count += 1
- html_file.write(self._sanitize_html(html.encode('utf-8')))
- html_file.close()
+ with tempfile.NamedTemporaryFile(suffix="%d.body.html" %count,
+ delete=False) as html_file:
+ count += 1
+ html_file.write(self._sanitize_html(html.encode('utf-8')))
file_to_del.append(html_file.name)
command.append(html_file.name)
command.append(out_filename)
@@ -227,9 +217,9 @@ class WebKitParser(report_sxw):
if status :
raise except_osv(_('Webkit error' ),
_("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message))
- pdf_file = open(out_filename, 'rb')
- pdf = pdf_file.read()
- pdf_file.close()
+ with open(out_filename, 'rb') as pdf_file:
+ pdf = pdf_file.read()
+ os.close(fd)
finally:
if stderr_fd is not None:
os.close(stderr_fd)
diff --git a/addons/sale/views/report_saleorder.xml b/addons/sale/views/report_saleorder.xml
index c23e680e664..5c198c635a3 100644
--- a/addons/sale/views/report_saleorder.xml
+++ b/addons/sale/views/report_saleorder.xml
@@ -19,7 +19,7 @@
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
- <div class="col-xs-4 col-xs-offset-2">
+ <div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id"
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
</div>
diff --git a/addons/survey/security/ir.model.access.csv b/addons/survey/security/ir.model.access.csv
index 89c001ce44e..e49c976ea1f 100644
--- a/addons/survey/security/ir.model.access.csv
+++ b/addons/survey/security/ir.model.access.csv
@@ -1,11 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_survey_public,survey.survey public,model_survey_survey,base.group_public,1,0,0,0
-access_survey_page_public,survey.page public,model_survey_page,base.group_public,1,0,0,0
-access_survey_question_public,survey.question public,model_survey_question,base.group_public,1,0,0,0
-access_survey_label_public,survey.label public,model_survey_label,base.group_public,1,0,0,0
-access_survey_user_input_public,survey.user_input public,model_survey_user_input,base.group_public,1,1,1,0
-access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,base.group_public,1,1,1,0
-access_survey_stage_public,survey.stage public,model_survey_stage,base.group_public,1,0,0,0
+access_survey_public,survey.survey public,model_survey_survey,,1,0,0,0
+access_survey_page_public,survey.page public,model_survey_page,,1,0,0,0
+access_survey_question_public,survey.question public,model_survey_question,,1,0,0,0
+access_survey_label_public,survey.label public,model_survey_label,,1,0,0,0
+access_survey_user_input_public,survey.user_input public,model_survey_user_input,,1,1,1,0
+access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,,1,1,1,0
+access_survey_stage_public,survey.stage public,model_survey_stage,,1,0,0,0
access_survey_user,survey.survey user,model_survey_survey,base.group_survey_user,1,0,0,0
access_survey_page_user,survey.page user,model_survey_page,base.group_survey_user,1,0,0,0
access_survey_question_user,survey.question user,model_survey_question,base.group_survey_user,1,0,0,0
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index ffe9d268240..a4ecc4e17d9 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -2596,6 +2596,7 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
this.$('.select_records').on('click', self.on_click);
},
on_click: function(ev) {
+ event.preventDefault();
var self = this;
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
this.pop = new instance.web.form.SelectCreatePopup(this);
@@ -2614,15 +2615,14 @@ instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(insta
});
}
else {
- var domain = ["id", "in", element_ids];
+ var domain = [["id", "in", element_ids]];
var domain_done = $.Deferred().resolve(domain);
}
$.when(domain_done).then(function (domain) {
var domain = self.pop.dataset.domain.concat(domain || []);
- self.set_value(JSON.stringify(domain))
+ self.set_value(domain);
});
});
- event.preventDefault();
},
});
@@ -4463,7 +4463,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
else
return $.when();
}).done(function () {
- if (!self.o2m.options.reload_on_button) {
+ var ds = self.o2m.dataset;
+ var cached_records = _.any([ds.to_create, ds.to_delete, ds.to_write], function(value) {
+ return value.length;
+ });
+ if (!self.o2m.options.reload_on_button && !cached_records) {
self.handle_button(name, id, callback);
}else {
self.handle_button(name, id, function(){
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index 753d52d497a..c6e28301c42 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -436,6 +436,16 @@ instance.web.ActionManager = instance.web.Widget.extend({
ir_actions_act_window: function (action, options) {
var self = this;
+ if (action.target === 'current'){
+ action.context['active_model'] = action.res_model;
+ if (action.res_id){
+ action.context['active_id'] = action.res_id;
+ action.context['active_ids'] = [action.res_id];
+ } else{
+ delete action.context['active_id'];
+ delete action.context['active_ids'];
+ }
+ }
return this.ir_actions_common({
widget: function () { return new instance.web.ViewManagerAction(self, action); },
action: action,
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 39f3067720f..446080875a6 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -378,7 +378,7 @@ class Website(openerp.addons.web.controllers.main.Home):
"""
response = werkzeug.wrappers.Response()
return request.registry['website']._image(
- request.cr, request.uid, model, id, field, response)
+ request.cr, request.uid, model, id, field, response, max_width, max_height)
#------------------------------------------------------
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 360b34c877c..44b3f771f62 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -141,49 +141,50 @@ class ir_http(orm.AbstractModel):
return response
def _handle_exception(self, exception=None, code=500):
- if isinstance(exception, werkzeug.exceptions.HTTPException) and hasattr(exception, 'response') and exception.response:
- return exception.response
-
- attach = self._serve_attachment()
- if attach:
- return attach
-
- if getattr(request, 'website_enabled', False) and request.website:
- values = dict(
- exception=exception,
- traceback=traceback.format_exc(exception),
- )
- if exception:
- code = getattr(exception, 'code', code)
- if isinstance(exception, ir_qweb.QWebException):
- values.update(qweb_exception=exception)
- if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
- code = 403
- if code == 500:
- logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
- if 'qweb_exception' in values:
- view = request.registry.get("ir.ui.view")
- views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
- to_reset = [v for v in views if v.model_data_id.noupdate is True]
- values['views'] = to_reset
- elif code == 403:
- logger.warn("403 Forbidden:\n\n%s", values['traceback'])
-
- values.update(
- status_message=werkzeug.http.HTTP_STATUS_CODES[code],
- status_code=code,
- )
-
- if not request.uid:
- self._auth_method_public()
-
- try:
- html = request.website._render('website.%s' % code, values)
- except Exception:
- html = request.website._render('website.http_error', values)
- return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
+ try:
+ return super(ir_http, self)._handle_exception(exception)
+ except Exception:
- return super(ir_http, self)._handle_exception(exception)
+ attach = self._serve_attachment()
+ if attach:
+ return attach
+
+ if getattr(request, 'website_enabled', False) and request.website:
+ values = dict(
+ exception=exception,
+ traceback=traceback.format_exc(exception),
+ )
+ if exception:
+ code = getattr(exception, 'code', code)
+ if isinstance(exception, ir_qweb.QWebException):
+ values.update(qweb_exception=exception)
+ if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
+ code = 403
+ if code == 500:
+ logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
+ if 'qweb_exception' in values:
+ view = request.registry.get("ir.ui.view")
+ views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
+ to_reset = [v for v in views if v.model_data_id.noupdate is True]
+ values['views'] = to_reset
+ elif code == 403:
+ logger.warn("403 Forbidden:\n\n%s", values['traceback'])
+
+ values.update(
+ status_message=werkzeug.http.HTTP_STATUS_CODES[code],
+ status_code=code,
+ )
+
+ if not request.uid:
+ self._auth_method_public()
+
+ try:
+ html = request.website._render('website.%s' % code, values)
+ except Exception:
+ html = request.website._render('website.http_error', values)
+ return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
+
+ raise
class ModelConverter(ir.ir_http.ModelConverter):
def __init__(self, url_map, model=False, domain='[]'):
diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py
index ad2b63a6dd9..0e1fe2beb89 100644
--- a/addons/website/models/ir_qweb.py
+++ b/addons/website/models/ir_qweb.py
@@ -15,6 +15,7 @@ import urllib2
import urlparse
import re
+import werkzeug.urls
import werkzeug.utils
from dateutil import parser
from lxml import etree, html
@@ -265,10 +266,19 @@ class Image(orm.AbstractModel):
if options is None: options = {}
classes = ['img', 'img-responsive'] + options.get('class', '').split()
- return ir_qweb.HTMLSafe('<img class="%s" src="/website/image?model=%s&field=%s&id=%s"/>' % (
+ url_params = {
+ 'model': record._model._name,
+ 'field': field_name,
+ 'id': record.id,
+ }
+ for options_key in ['max_width', 'max_height']:
+ if options.get(options_key):
+ url_params[options_key] = options[options_key]
+
+ return ir_qweb.HTMLSafe('<img class="%s" src="/website/image?%s"/>' % (
' '.join(itertools.imap(werkzeug.utils.escape, classes)),
- record._model._name,
- field_name, record.id))
+ werkzeug.urls.url_encode(url_params)
+ ))
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
def from_html(self, cr, uid, model, column, element, context=None):
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 433d8644698..db02dcce51e 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -541,7 +541,10 @@ class website(osv.osv):
response.mimetype = Image.MIME[image.format]
w, h = image.size
- max_w, max_h = int(max_width), int(max_height)
+ try:
+ max_w, max_h = int(max_width), int(max_height)
+ except:
+ max_w, max_h = (maxint, maxint)
if w < max_w and h < max_h:
response.data = data
diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js
index 8588b7a3765..1c4bad90ffc 100644
--- a/addons/website/static/src/js/website.seo.js
+++ b/addons/website/static/src/js/website.seo.js
@@ -264,21 +264,21 @@
},
description: function () {
var $description = $('meta[name=description]');
- return ($description.length > 0) && ($description.attr('value') && $description.attr('value').trim());
+ return ($description.length > 0) && ($description.attr('content') && $description.attr('content').trim());
},
changeDescription: function (description) {
// TODO create tag if missing
- $('meta[name=description]').attr('value', description);
+ $('meta[name=description]').attr('content', description);
this.trigger('description-changed', description);
},
keywords: function () {
var $keywords = $('meta[name=keywords]');
- var parsed = ($keywords.length > 0) && $keywords.attr('value') && $keywords.attr('value').split(",");
+ var parsed = ($keywords.length > 0) && $keywords.attr('content') && $keywords.attr('content').split(",");
return (parsed && parsed[0]) ? parsed: [];
},
changeKeywords: function (keywords) {
// TODO create tag if missing
- $('meta[name=keywords]').attr('value', keywords.join(","));
+ $('meta[name=keywords]').attr('content', keywords.join(","));
this.trigger('keywords-changed', keywords);
},
headers: function (tag) {
@@ -296,7 +296,7 @@
});
},
company: function () {
- return $('meta[name="openerp.company"]').attr('value');
+ return $('html').attr('data-oe-company-name');
},
bodyText: function () {
return $('body').children().not('.js_seo_configuration').text();
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 6226e3a0aa7..56d954e8a35 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -61,8 +61,10 @@
t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None"
t-att-data-view-xmlid="xmlid if editable else None"
- t-att-data-main-object="repr(main_object) if editable else None">
+ t-att-data-main-object="repr(main_object) if editable else None"
+ t-att-data-oe-company-name="res_company.name">
<head>
+ <meta charset="utf-8" />
<t t-if="main_object and 'website_meta_title' in main_object">
<t t-set="title" t-value="main_object.website_meta_title"/>
</t>
@@ -73,10 +75,9 @@
<t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t>
</t>
<meta name="viewport" content="initial-scale=1"/>
- <meta name="openerp.company" t-att-value="res_company.name"/>
- <meta name="description" t-att-value="main_object and 'website_meta_description' in main_object
+ <meta name="description" t-att-content="main_object and 'website_meta_description' in main_object
and main_object.website_meta_description or website_meta_description"/>
- <meta name="keywords" t-att-value="main_object and 'website_meta_keywords' in main_object
+ <meta name="keywords" t-att-content="main_object and 'website_meta_keywords' in main_object
and main_object.website_meta_keywords or website_meta_keywords"/>
<title><t t-esc="title"/></title>
@@ -137,14 +138,14 @@
<footer>
<div class="container hidden-print" id="footer_container">
<div class="row">
- <div class="col-md-3" name="product">
+ <div class="col-md-3">
<h4>Our products &amp; Services</h4>
<ul class="list-unstyled" name="products">
<li><a href="/">Home</a></li>
</ul>
</div>
<div class="col-md-3" name="info">
- <h4 name="info_title">Connect with us</h4>
+ <h4>Connect with us</h4>
<ul class="list-unstyled">
<li><a href="/page/website.contactus">Contact us</a></li>
</ul>
@@ -161,7 +162,7 @@
<a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
</h2>
</div>
- <div class="col-md-5 col-lg-offset-1" name="about_us">
+ <div class="col-md-5 col-lg-offset-1">
<div>
<h4>
<span t-field="res_company.name">Your Company</span>
@@ -306,7 +307,7 @@
<template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger &amp; Tests">
<xpath expr='//t[@name="layout_head"]' position="after">
- <script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
+ <t t-set="debugger_hook" t-value="1" />
</xpath>
</template>
diff --git a/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml b/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml
index f960511edfc..06b5317c4ee 100644
--- a/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml
+++ b/addons/website_crm_partner_assign/views/website_crm_partner_assign.xml
@@ -86,8 +86,8 @@
</t>
<div class="media">
<a class="pull-left" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}"
- t-field="partner.image_small"
- t-field-options='{"widget": "image", "class": "media-object"}'
+ t-field="partner.image"
+ t-field-options='{"widget": "image", "class": "media-object", "max_width": 128}'
></a>
<div class="media-body o_partner_body" style="min-height: 64px;">
<a class="media-heading" t-attf-href="/partners/#{slug(partner)}?#{current_grade and 'grade_id=%s&amp;' % current_grade.id}#{current_country and 'country_id=%s' % current_country.id}">
diff --git a/addons/website_event_sale/views/website_event_sale.xml b/addons/website_event_sale/views/website_event_sale.xml
index 967d9c029cc..cf46797b34d 100644
--- a/addons/website_event_sale/views/website_event_sale.xml
+++ b/addons/website_event_sale/views/website_event_sale.xml
@@ -3,7 +3,7 @@
<data>
<template id="debugger" inherit_id="website.debugger" name="Event Debugger">
- <xpath expr="//script[last()]" position="after">
+ <xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script>
</xpath>
</template>
diff --git a/addons/website_event_track/controllers/event.py b/addons/website_event_track/controllers/event.py
index f0a9efa8dfc..b2e689e5cb6 100644
--- a/addons/website_event_track/controllers/event.py
+++ b/addons/website_event_track/controllers/event.py
@@ -88,10 +88,21 @@ class website_event(http.Controller):
days_tracks_count[day] = len(tracks)
days[day] = self._prepare_calendar(event, tracks)
+ cr, uid, context = request.cr, request.uid, request.context
+ track_obj = request.registry['event.track']
+ tracks_ids = track_obj.search(cr, openerp.SUPERUSER_ID, [('event_id', '=', event.id)], context=context)
+ speakers = dict()
+ for t in track_obj.browse(cr, openerp.SUPERUSER_ID, tracks_ids, context=context):
+ acc = ""
+ for speaker in t.speaker_ids:
+ acc = speaker.name + u" – " + acc if acc else speaker.name
+ speakers[t.id] = acc
+
return request.website.render("website_event_track.agenda", {
'event': event,
'days': days,
'days_nbr': days_tracks_count,
+ 'speakers': speakers,
'tag': tag
})
diff --git a/addons/website_event_track/views/website_event.xml b/addons/website_event_track/views/website_event.xml
index cb514028266..9aece0f5c11 100644
--- a/addons/website_event_track/views/website_event.xml
+++ b/addons/website_event_track/views/website_event.xml
@@ -85,8 +85,8 @@
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
<span t-esc="track and track.name"/>
</a>
- <div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker">
- <small t-esc="speaker.display_name"/>
+ <div class="text-muted">
+ <small t-esc="speakers[track.id]"/>
</div>
</t>
</td>
@@ -98,11 +98,11 @@
<t t-set="track" t-value="dt[1][False][0]"/>
<td t-att-colspan="len(locations)-1" t-attf-class="text-center event_color_#{track.color} #{track and 'event_track' or ''}">
<a t-attf-href="/event/#{ slug(event) }/track/#{ slug(track) }">
- <span t-esc="track.name"/><br/>
- <div class="text-muted" t-foreach="track.speaker_ids" t-as="speaker">
- <small t-esc="speaker.display_name"/>
- </div>
+ <span t-esc="track.name"/>
</a>
+ <div class="text-muted">
+ <small t-esc="speakers[track.id]"/>
+ </div>
</td>
</t>
</tr>
diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py
index 53397f38b63..81cbfdcadc8 100644
--- a/addons/website_forum/controllers/main.py
+++ b/addons/website_forum/controllers/main.py
@@ -330,13 +330,12 @@ class WebsiteForum(http.Controller):
cr, uid, context = request.cr, request.uid, request.context
if kwargs.get('comment') and post.forum_id.id == forum.id:
# TDE FIXME: check that post_id is the question or one of its answers
- if request.registry['res.users'].has_group(cr, uid, 'website_mail.group_comment'):
- request.registry['forum.post'].message_post(
- cr, uid, post.id,
- body=kwargs.get('comment'),
- type='comment',
- subtype='mt_comment',
- context=dict(context, mail_create_nosubcribe=True))
+ request.registry['forum.post'].message_post(
+ cr, uid, post.id,
+ body=kwargs.get('comment'),
+ type='comment',
+ subtype='mt_comment',
+ context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
diff --git a/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml b/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml
index 792995ca606..5d47e69fead 100644
--- a/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml
+++ b/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml
@@ -11,6 +11,26 @@
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
+ <record id="hr_job_portal" model="ir.rule">
+ <field name="name">Job Positions: Portal</field>
+ <field name="model_id" ref="hr.model_hr_job"/>
+ <field name="domain_force">[('website_published', '=', True)]</field>
+ <field name="groups" eval="[(4, ref('base.group_portal'))]"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_unlink" eval="False"/>
+ </record>
+ <record id="hr_job_officer" model="ir.rule">
+ <field name="name">Job Positions: HR Officer</field>
+ <field name="model_id" ref="hr.model_hr_job"/>
+ <field name="domain_force">[(1, '=', 1)]</field>
+ <field name="groups" eval="[(4, ref('base.group_hr_user'))]"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_unlink" eval="False"/>
+ </record>
<record id="hr_department_public" model="ir.rule">
<field name="name">Job department: Public</field>
<field name="model_id" ref="hr.model_hr_department"/>
diff --git a/addons/website_mail/controllers/main.py b/addons/website_mail/controllers/main.py
index 0932794ee28..de69fba490a 100644
--- a/addons/website_mail/controllers/main.py
+++ b/addons/website_mail/controllers/main.py
@@ -62,6 +62,7 @@ class WebsiteMail(http.Controller):
@http.route(['/website_mail/is_follower'], type='json', auth="public", website=True)
def call(self, model, id, **post):
+ id = int(id)
cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry.get('res.partner')
@@ -74,17 +75,28 @@ class WebsiteMail(http.Controller):
partner_id = users_obj.browse(cr, SUPERUSER_ID, uid, context).partner_id
elif request.session.get('partner_id'):
partner_id = partner_obj.browse(cr, SUPERUSER_ID, request.session.get('partner_id'), context)
-
- email = ""
- is_follower = False
- if partner_id:
- email = partner_id and partner_id.email
- is_follower = partner_id.id in [
- fol.id for fol in obj.browse(cr, SUPERUSER_ID, id, context).message_follower_ids]
+ email = partner_id and partner_id.email or ""
- return {
+ values = {
'is_user': uid != public_id,
'email': email,
- 'is_follower': is_follower
+ 'is_follower': False,
}
+ if not obj:
+ return values
+ obj_ids = obj.exists(cr, SUPERUSER_ID, [id], context=context)
+ if obj_ids:
+ if partner_id:
+ values['is_follower'] = len(
+ request.registry['mail.followers'].search(
+ cr, SUPERUSER_ID, [
+ ('res_model', '=', 'mail.group'),
+ ('res_id', '=', obj_ids[0]),
+ ('partner_id', '=', partner_id.id)
+ ], context=context)) == 1
+ if post.get('fields'):
+ record = obj.read(cr, SUPERUSER_ID, obj_ids[0], fields=post.get('fields'), context=context)
+ values.update(record)
+
+ return values
diff --git a/addons/website_mail/models/mail_message.py b/addons/website_mail/models/mail_message.py
index 1e50611fa00..cff950f2962 100644
--- a/addons/website_mail/models/mail_message.py
+++ b/addons/website_mail/models/mail_message.py
@@ -34,7 +34,7 @@ class MailMessage(osv.Model):
res[message.id] = message.subject
else:
plaintext_ct = html2plaintext(message.body)
- res[message.id] = plaintext_ct + '%s' % (' [...]' if len(plaintext_ct) >= 20 else '')
+ res[message.id] = plaintext_ct[:30] + '%s' % (' [...]' if len(plaintext_ct) >= 30 else '')
return res
_columns = {
diff --git a/addons/website_mail/static/src/js/website_mail.js b/addons/website_mail/static/src/js/website_mail.js
index bd34b526ddd..d069f88ea45 100644
--- a/addons/website_mail/static/src/js/website_mail.js
+++ b/addons/website_mail/static/src/js/website_mail.js
@@ -7,17 +7,20 @@
selector: ".js_follow",
start: function (editable_mode) {
var self = this;
+ this.is_user = false;
- // set value and display button
- self.$target.find("input").removeClass("hidden");
openerp.jsonRpc('/website_mail/is_follower', 'call', {
model: this.$target.data('object'),
- id: +this.$target.data('id'),
+ id: this.$target.data('id'),
+ fields: ['name', 'alias_id'],
}).always(function (data) {
+ self.is_user = data.is_user;
+ self.$target.find('.js_mg_email').attr('href', 'mailto:' + data.alias_id[1]);
+ self.$target.find('.js_mg_link').attr('href', '/groups/' + data.id);
+ self.toggle_subscription(data.is_follower);
self.$target.find('input.js_follow_email')
.val(data.email ? data.email : "")
- .attr("disabled", data.is_follower && data.email.length ? "disabled" : false);
- self.$target.attr("data-follow", data.is_follower ? 'on' : 'off');
+ .attr("disabled", data.is_follower || (data.email.length && self.is_user) ? "disabled" : false);
self.$target.removeClass("hidden");
});
@@ -30,10 +33,11 @@
self.on_click();
});
}
+ return;
},
on_click: function () {
var self = this;
- var $email = this.$target.find(".js_follow_email:visible");
+ var $email = this.$target.find(".js_follow_email");
if ($email.length && !$email.val().match(/.+@.+/)) {
this.$target.addClass('has-error');
@@ -47,13 +51,27 @@
'message_is_follower': this.$target.attr("data-follow") || "off",
'email': $email.length ? $email.val() : false,
}).then(function (follow) {
- if (follow) {
- self.$target.find(".js_follow_email, .input-group-btn").addClass("hidden");
- self.$target.find(".alert").removeClass("hidden");
- }
- self.$target.find('input.js_follow_email').attr("disabled", follow ? "disabled" : false);
- self.$target.attr("data-follow", follow ? 'on' : 'off');
+ self.toggle_subscription(follow);
});
},
+ toggle_subscription: function(follow) {
+ if (follow) {
+ this.$target.find(".js_mg_follow_form").addClass("hidden");
+ this.$target.find(".js_mg_details").removeClass("hidden");
+ }
+ else {
+ this.$target.find(".js_mg_follow_form").removeClass("hidden");
+ this.$target.find(".js_mg_details").addClass("hidden");
+ }
+ this.$target.find('input.js_follow_email').attr("disabled", follow || this.is_user ? "disabled" : false);
+ this.$target.attr("data-follow", follow ? 'on' : 'off');
+ },
+ });
+
+ $(document).ready(function () {
+ $('.js_follow_btn').on('click', function (ev) {
+ var email = $(ev.currentTarget).parents('.js_mg_follow_form').first().find('.js_follow_email').val();
+ $(document).find('.js_follow_email').val(email);
+ });
});
})();
diff --git a/addons/website_mail/views/snippets.xml b/addons/website_mail/views/snippets.xml
index 98a79393211..987ccaa672e 100644
--- a/addons/website_mail/views/snippets.xml
+++ b/addons/website_mail/views/snippets.xml
@@ -503,20 +503,25 @@
<span class="oe_snippet_thumbnail_title">Subscribe Button</span>
</div>
- <div class="oe_snippet_body input-group js_follow"
+ <div class="oe_snippet_body js_follow"
data-id="0"
data-object="mail.group"
data-follow="off">
- <input
- type="email"
- name="email"
- class="js_follow_email form-control"
- placeholder="your email..."/>
- <span class="input-group-btn">
- <a href="#" class="btn btn-default js_unfollow_btn">Unsubscribe</a>
- <a href="#" class="btn btn-primary js_follow_btn">Subscribe</a>
- </span>
- <div class="alert alert-success hidden">thanks for your subscription!</div>
+ <div class="input-group js_mg_follow_form">
+ <input
+ type="email"
+ name="email"
+ class="js_follow_email form-control"
+ placeholder="your email..."/>
+ <span class="input-group-btn">
+ <button href="#" class="btn btn-primary js_follow_btn">Subscribe</button>
+ </span>
+ </div>
+ <p class="js_mg_details hidden well well-sm">
+ <i class="fa fa-envelope-o"/><a href="#" class="js_mg_email"> send mail</a> -
+ <i class="fa fa-file-o"/><a href="#" class="js_mg_link"> archives</a> -
+ <i class="fa fa-times"/><a href="#" class="js_unfollow_btn"> unsubscribe</a>
+ </p>
</div>
</div>
diff --git a/addons/website_mail_group/controllers/main.py b/addons/website_mail_group/controllers/main.py
index 88eb1a7e9b7..7320ebc6548 100644
--- a/addons/website_mail_group/controllers/main.py
+++ b/addons/website_mail_group/controllers/main.py
@@ -9,13 +9,14 @@ from openerp.addons.web.http import request
class MailGroup(http.Controller):
- _thread_per_page = 10
+ _thread_per_page = 20
+ _replies_per_page = 10
def _get_archives(self, group_id):
MailMessage = request.registry['mail.message']
groups = MailMessage.read_group(
request.cr, request.uid, [('model', '=', 'mail.group'), ('res_id', '=', group_id)], ['subject', 'date'],
- groupby="date", orderby="date asc", context=request.context)
+ groupby="date", orderby="date desc", context=request.context)
for group in groups:
begin_date = datetime.datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
end_date = datetime.datetime.strptime(group['__domain'][1][2], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
@@ -27,7 +28,7 @@ class MailGroup(http.Controller):
def view(self, **post):
cr, uid, context = request.cr, request.uid, request.context
group_obj = request.registry.get('mail.group')
- group_ids = group_obj.search(cr, uid, [], context=context)
+ group_ids = group_obj.search(cr, uid, [('alias_id', '!=', False), ('alias_id.alias_name', '!=', False)], context=context)
values = {'groups': group_obj.browse(cr, uid, group_ids, context)}
return request.website.render('website_mail_group.mail_groups', values)
@@ -73,6 +74,7 @@ class MailGroup(http.Controller):
'archives': self._get_archives(group.id),
'date_begin': date_begin,
'date_end': date_end,
+ 'replies_per_page': self._replies_per_page,
}
return request.website.render('website_mail_group.group_messages', values)
@@ -81,11 +83,49 @@ class MailGroup(http.Controller):
], type='http', auth="public", website=True)
def thread_discussion(self, group, message, mode='thread', date_begin=None, date_end=None, **post):
cr, uid, context = request.cr, request.uid, request.context
+ Message = request.registry['mail.message']
+ if mode == 'thread':
+ base_domain = [('model', '=', 'mail.group'), ('res_id', '=', group.id), ('parent_id', '=', message.parent_id and message.parent_id.id or False)]
+ else:
+ base_domain = [('model', '=', 'mail.group'), ('res_id', '=', group.id)]
+ next_message = None
+ next_message_ids = Message.search(cr, uid, base_domain + [('date', '<', message.date)], order="date DESC", limit=1, context=context)
+ if next_message_ids:
+ next_message = Message.browse(cr, uid, next_message_ids[0], context=context)
+ prev_message = None
+ prev_message_ids = Message.search(cr, uid, base_domain + [('date', '>', message.date)], order="date ASC", limit=1, context=context)
+ if prev_message_ids:
+ prev_message = Message.browse(cr, uid, prev_message_ids[0], context=context)
values = {
'message': message,
'group': group,
'mode': mode,
+ 'archives': self._get_archives(group.id),
'date_begin': date_begin,
'date_end': date_end,
+ 'replies_per_page': self._replies_per_page,
+ 'next_message': next_message,
+ 'prev_message': prev_message,
}
return request.website.render('website_mail_group.group_message', values)
+
+ @http.route(
+ '''/groups/<model('mail.group'):group>/<model('mail.message', "[('model','=','mail.group'), ('res_id','=',group[0])]"):message>/get_replies''',
+ type='json', auth="public", methods=['POST'], website=True)
+ def render_messages(self, group, message, **post):
+ last_displayed_id = post.get('last_displayed_id')
+ if not last_displayed_id:
+ return False
+ Message = request.registry['mail.message']
+ replies_domain = [('id', '<', int(last_displayed_id)), ('parent_id', '=', message.id)]
+ msg_ids = Message.search(request.cr, request.uid, replies_domain, limit=self._replies_per_page, context=request.context)
+ msg_count = Message.search(request.cr, request.uid, replies_domain, count=True, context=request.context)
+ messages = Message.browse(request.cr, request.uid, msg_ids, context=request.context)
+ values = {
+ 'group': group,
+ 'thread_header': message,
+ 'messages': messages,
+ 'msg_more_count': msg_count - self._replies_per_page,
+ 'replies_per_page': self._replies_per_page,
+ }
+ return request.registry['ir.ui.view'].render(request.cr, request.uid, 'website_mail_group.messages_short', values, engine='ir.qweb', context=request.context)
diff --git a/addons/website_mail_group/static/src/css/website_mail_group.css b/addons/website_mail_group/static/src/css/website_mail_group.css
new file mode 100644
index 00000000000..27482ebd203
--- /dev/null
+++ b/addons/website_mail_group/static/src/css/website_mail_group.css
@@ -0,0 +1,12 @@
+.o_mg_avatar {
+ width: 40px;
+ height: 40px;
+}
+
+.o_mg_link_show {
+ display: none;
+}
+
+.o_mg_link_content {
+ display: none;
+}
diff --git a/addons/website_mail_group/static/src/js/website_mail_group.js b/addons/website_mail_group/static/src/js/website_mail_group.js
new file mode 100644
index 00000000000..c9b8ae797ed
--- /dev/null
+++ b/addons/website_mail_group/static/src/js/website_mail_group.js
@@ -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;
+ });
+ });
+}); \ No newline at end of file
diff --git a/addons/website_mail_group/views/website_mail_group.xml b/addons/website_mail_group/views/website_mail_group.xml
index 87f8755c04a..76edd524753 100644
--- a/addons/website_mail_group/views/website_mail_group.xml
+++ b/addons/website_mail_group/views/website_mail_group.xml
@@ -1,152 +1,240 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
- <template id="footer_mailing_list" inherit_id="website.layout" name="Footer Mailing List Link">
- <xpath expr="//footer//div[@name='info']/ul" position="inside">
- <li><a t-attf-href="/groups">Mailing List</a></li>
- </xpath>
- </template>
- <template id="mail_groups" name="Mailing Lists">
- <t t-call="website.layout">
- <div class="container">
- <h1>
- Our Mailing Lists
- </h1>
- <div class="row">
- <div class="col-sm-4" style="height: 140px" t-foreach="groups" t-as="group">
- <img t-att-src="'/website/image?model=mail.group&amp;field=image_small&amp;id='+str(group['id'])" class="pull-left"/>
- <div>
- <strong><a t-attf-href="/groups/#{ slug(group) }" t-esc="group.name"/></strong>
- <div t-esc="group.description" class="text-muted"/>
- <t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
- </div>
- </div>
+
+<template id="footer_mailing_list" inherit_id="website.layout" name="Footer Mailing List Link">
+ <xpath expr="//footer//div[@name='info']/ul" position="inside">
+ <li><a t-attf-href="/groups">Mailing List</a></li>
+ </xpath>
+</template>
+
+<template id="mail_groups" name="Mailing Lists">
+ <t t-call="website.layout">
+ <div id="wrap" class="oe_structure oe_empty">
+ <section class="bg-primary jumbotron mt0 mb0">
+ <div class="container">
+ <h1>Stay in touch with our Community</h1>
+ <p>Alone we can do so little, together we can do so much</p>
+ </div>
+ </section>
+ </div>
+ <div class="container mt32">
+ <div class="row mt8" t-foreach="groups" t-as="group">
+ <div class="col-md-3">
+ <img t-att-src="'/website/image?model=mail.group&amp;field=image_small&amp;id='+str(group['id'])" class="pull-left"/>
+ <strong><a t-attf-href="/groups/#{ slug(group) }" t-esc="group.name"/></strong><br />
+ <i class='fa fa-envelope-o'/>
+ <a t-attf-href="mailto:#{group.alias_id.alias_name}@#{group.alias_id.alias_domain}"><span t-field="group.alias_id"/></a>
+ </div>
+ <div class="col-md-4">
+ <div t-esc="group.description" class="text-muted"/>
+ </div>
+ <div class="col-md-2">
+ <i class='fa fa-user'/> <t t-esc="len(group.message_follower_ids)"/> participants<br />
+ <i class='fa fa-envelope-o'/> <t t-esc="len(group.message_ids)"/> messages
+ </div>
+ <div class="col-md-3">
+ <t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>
</div>
</div>
- </t>
- </template>
+ </div>
+ </t>
+</template>
- <template id="group_messages" name="Message Threads">
- <t t-call="website.layout">
- <section class="container">
- <div class="row mt8">
- <div class="col-md-5 mt16">
- <ol class="breadcrumb">
- <li><a href="/groups">Mailing Lists</a></li>
- <li class="active" t-esc="group.name"/>
- </ol>
- </div>
- <div class="col-md-5 pull-right">
- <t t-call="website.pager"/>
- </div>
+<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>
- <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 class="col-md-9">
+ <div>
+ <t t-call="website.pager"/>
</div>
- </div>
- </section>
- </t>
- </template>
-
- <template id="group_message">
- <t t-call="website.layout">
- <div class="container">
- <div class="row mt8">
- <div class="col-md-5">
- <ol class="breadcrumb mb8">
- <li><a href="/groups">Mailing Lists</a></li>
- <li>
- <a t-attf-href="/groups/#{slug(group)}?mode=#{mode}&amp;date_begin=#{date_begin}&amp;date_end=#{date_end}">
- <span t-field="group.name"/>
- </a>
- </li>
- <li class="active" t-esc="message.subject or 'Message'"/>
- </ol>
+ <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>
- <h1 t-field="message.subject"/>
- <img class="img-rounded pull-left" t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(message.id)" style="width : 30px"/>
- <h4 class="mt0 mb32">
- <t t-if="message.author_id">
- <span t-field="message.author_id" style="display: inline-block;" t-field-options='{
- "widget": "contact",
- "fields": ["name"]
- }'/>
- </t>
- <t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
- on <span t-field="message.date"/>
+<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 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 t-if="message.child_ids">
- <h2 class="page-header">Follow ups</h2>
- <t t-set="messages" t-value="message.child_ids"/>
- <t t-call="website_mail_group.messages_short"/>
- </div>
- <div t-if="message.parent_id">
- <h2 class="page-header">Reference</h2>
- <t t-set="messages" t-value="[message.parent_id]"/>
- <t t-call="website_mail_group.messages_short"/>
+ </div>
+ <div class="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 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>
+ <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>
- </t>
- </template>
+ </section>
+ </t>
+</template>
- <template id="messages_short">
+<template id="messages_short">
+ <div>
<ul class="media-list">
<li t-foreach="messages" t-as="thread" class="media">
- <img class="img-rounded pull-left mt0 media-object" style="height: 40px"
+ <img class="img-rounded pull-left mt0 media-object o_mg_avatar"
t-att-src="'/website/image?model=mail.message&amp;field=author_avatar&amp;id='+str(thread.id)"/>
<div class="media-body">
<h4 class="media-heading">
@@ -161,14 +249,36 @@
}'/>
</t>
<t t-if="not thread.author_id"><t t-esc="thread.email_from"/></t>
- <span class="fa fa-comment-o">
- <t t-raw="len(thread.child_ids)"/> replies
- </span>
+ - <i class="fa fa-calendar"/> <t t-esc="thread.date"/>
+ - <i class="fa fa-paperclip"/> <t t-esc="len(thread.attachment_ids)"/>
</small>
+ <p t-if="thread.child_ids" class="mt8">
+ <a href="#" class="o_mg_link_hide">
+ <i class="fa fa-chevron-right"/> <t t-raw="len(thread.child_ids)"/> replies
+ </a>
+ <a href="#" class="o_mg_link_show">
+ <i class="fa fa-chevron-down"/> <t t-raw="len(thread.child_ids)"/> replies
+ </a>
+ </p>
+ <div class="o_mg_link_content o_mg_replies">
+ <t t-call="website_mail_group.messages_short">
+ <t t-set="messages" t-value="thread.child_ids[:replies_per_page]"/>
+ <t t-set="msg_more_count" t-value="len(thread.child_ids) - replies_per_page"/>
+ <t t-set="thread_header" t-value="thread"/>
+ </t>
+ </div>
</div>
</li>
</ul>
- </template>
+ <p t-if="messages and msg_more_count > 0 and thread_header" class="well well-sm">
+ <button class="fa btn-link o_mg_read_more"
+ t-attf-data-href="/groups/#{slug(group)}/#{slug(thread_header)}/get_replies"
+ t-attf-data-msg-id="#{messages[-1].id}">
+ show <t t-esc="msg_more_count"/> more replies
+ </button>
+ </p>
+ </div>
+</template>
</data>
</openerp>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index bbd8f83fd7d..6df34f232b4 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -35,8 +35,8 @@ class table_compute(object):
index = 0
maxy = 0
for p in products:
- x = p.website_size_x
- y = p.website_size_y
+ x = min(max(p.website_size_x, 1), PPR)
+ y = min(max(p.website_size_y, 1), PPR)
if index>PPG:
x = y = 1
diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py
index daa2a61c851..0911887c835 100644
--- a/addons/website_sale/models/product.py
+++ b/addons/website_sale/models/product.py
@@ -65,6 +65,7 @@ class product_template(osv.Model):
_inherit = ["product.template", "website.seo.metadata"]
_order = 'website_published desc, website_sequence desc, name'
_name = 'product.template'
+ _mail_post_access = 'read'
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, '')
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 0c69ece50cc..8e96628c38b 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -5,7 +5,7 @@
<!-- Layout and common templates -->
<template id="debugger" inherit_id="website.debugger" name="Event Debugger">
- <xpath expr="//script[last()]" position="after">
+ <xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_sale/static/src/js/website.tour.sale.js"></script>
</xpath>
</template>
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index 57f24c8864a..e9de9dc7028 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -41,6 +41,7 @@ import openerp.workflow
_logger = logging.getLogger(__name__)
+
class actions(osv.osv):
_name = 'ir.actions.actions'
_table = 'ir_actions'
@@ -129,9 +130,15 @@ class ir_actions_report_xml(osv.osv):
Look up a report definition and render the report for the provided IDs.
"""
new_report = self._lookup_report(cr, name)
- # in order to use current yml test files with qweb reports
- if isinstance(new_report, (str, unicode)):
- return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf'
+
+ if isinstance(new_report, (str, unicode)): # Qweb report
+ # The only case where a QWeb report is rendered with this method occurs when running
+ # yml tests originally written for RML reports.
+ if openerp.tools.config['test_enable'] and not tools.config['test_report_directory']:
+ # Only generate the pdf when a destination folder has been provided.
+ return self.pool['report'].get_html(cr, uid, res_ids, new_report, data=data, context=context), 'html'
+ else:
+ return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf'
else:
return new_report.create(cr, uid, res_ids, data, context)
@@ -1182,7 +1189,4 @@ class ir_actions_act_client(osv.osv):
}
-
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/openerp/http.py b/openerp/http.py
index 0627c769555..d0e4cf4d2eb 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -269,7 +269,10 @@ class WebRequest(object):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
be used as response."""
- raise
+ self._failed = exception # prevent tx commit
+ if isinstance(exception, werkzeug.exceptions.HTTPException):
+ return exception
+ raise
def _call_function(self, *args, **kwargs):
request = self
@@ -456,18 +459,20 @@ class JsonRequest(WebRequest):
def _handle_exception(self, exception):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
- be used as response."""
- _logger.exception("Exception during JSON request handling.")
- self._failed = exception # prevent tx commit
- error = {
- 'code': 200,
- 'message': "OpenERP Server Error",
- 'data': serialize_exception(exception)
- }
- if isinstance(exception, AuthenticationError):
- error['code'] = 100
- error['message'] = "OpenERP Session Invalid"
- return self._json_response(error=error)
+ be used as response."""
+ try:
+ return super(JsonRequest, self)._handle_exception(exception)
+ except Exception:
+ _logger.exception("Exception during JSON request handling.")
+ error = {
+ 'code': 200,
+ 'message': "OpenERP Server Error",
+ 'data': serialize_exception(exception)
+ }
+ if isinstance(exception, AuthenticationError):
+ error['code'] = 100
+ error['message'] = "OpenERP Session Invalid"
+ return self._json_response(error=error)
def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request
diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py
index 6ce69578a8e..af046836192 100644
--- a/openerp/report/render/rml2pdf/trml2pdf.py
+++ b/openerp/report/render/rml2pdf/trml2pdf.py
@@ -107,7 +107,7 @@ class NumberedCanvas(canvas.Canvas):
self.setFont("Helvetica", 8)
self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
" %(this)i / %(total)i" % {
- 'this': self._pageNumber+1,
+ 'this': self._pageNumber,
'total': page_count,
}
)
diff --git a/openerp/tools/test_reports.py b/openerp/tools/test_reports.py
index c008c20ae29..305d2b0f115 100644
--- a/openerp/tools/test_reports.py
+++ b/openerp/tools/test_reports.py
@@ -87,8 +87,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re
if ('[[' in line) or ('[ [' in line):
_logger.error("Report %s may have bad expression near: \"%s\".", rname, line[80:])
# TODO more checks, what else can be a sign of a faulty report?
- elif res_format == 'foobar':
- # TODO
+ elif res_format == 'html':
pass
else:
_logger.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format)