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

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 @@
- + 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 @@
-
+
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 @@ Address -
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 @@
-
+
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 @@
- + updated document @@ -293,7 +293,7 @@ - + , 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 @@

VAT:

-
+
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 Binary files /dev/null and b/addons/pad/static/src/img/pad_link_companies.jpeg 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 @@

  REFERENCES
  Order number: ${object.name}
-   Order total: ${object.amount_total} ${object.pricelist_id.currency_id.name}
+   Order total: ${object.amount_total} ${object.currency_id.name}
  Order date: ${object.date_order}
% if object.origin:   Order reference: ${object.origin}
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 @@

VAT:

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

VAT:

-
+
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='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: 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 @@ 0 Portrait 40 - 20 + 23 7 7 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.

\ - wkhtmltopdf.org'), 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.

wkhtmltopdf.org'), 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.

\ +wkhtmltopdf.org'), 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.

wkhtmltopdf.org'), 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"> @@ -19,7 +20,6 @@ -
-
+
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 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 @@

VAT:

-
+
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 + try: + return super(ir_http, self)._handle_exception(exception) + except Exception: - attach = self._serve_attachment() - if attach: - return attach + attach = self._serve_attachment() + if attach: + return attach - if getattr(request, 'website_enabled', False) and request.website: - values = dict( - exception=exception, - traceback=traceback.format_exc(exception), - ) - if exception: - code = getattr(exception, 'code', code) - if isinstance(exception, ir_qweb.QWebException): - values.update(qweb_exception=exception) - if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError): - code = 403 - if code == 500: - logger.error("500 Internal Server Error:\n\n%s", values['traceback']) - if 'qweb_exception' in values: - view = request.registry.get("ir.ui.view") - views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context) - to_reset = [v for v in views if v.model_data_id.noupdate is True] - values['views'] = to_reset - elif code == 403: - logger.warn("403 Forbidden:\n\n%s", values['traceback']) + if getattr(request, 'website_enabled', False) and request.website: + values = dict( + exception=exception, + traceback=traceback.format_exc(exception), + ) + if exception: + code = getattr(exception, 'code', code) + if isinstance(exception, ir_qweb.QWebException): + values.update(qweb_exception=exception) + if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError): + code = 403 + if code == 500: + logger.error("500 Internal Server Error:\n\n%s", values['traceback']) + if 'qweb_exception' in values: + view = request.registry.get("ir.ui.view") + views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context) + to_reset = [v for v in views if v.model_data_id.noupdate is True] + values['views'] = to_reset + elif code == 403: + logger.warn("403 Forbidden:\n\n%s", values['traceback']) - values.update( - status_message=werkzeug.http.HTTP_STATUS_CODES[code], - status_code=code, - ) + values.update( + status_message=werkzeug.http.HTTP_STATUS_CODES[code], + status_code=code, + ) - if not request.uid: - self._auth_method_public() + if not request.uid: + self._auth_method_public() - try: - html = request.website._render('website.%s' % code, values) - except Exception: - html = request.website._render('website.http_error', values) - return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') + try: + html = request.website._render('website.%s' % code, values) + except Exception: + html = request.website._render('website.http_error', values) + return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') - return super(ir_http, self)._handle_exception(exception) + raise class ModelConverter(ir.ir_http.ModelConverter): def __init__(self, url_map, model=False, domain='[]'): 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('' % ( + 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('' % ( ' '.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[^]]+)/static/(?P.+)$') 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"> + @@ -73,10 +75,9 @@ - - - - <t t-esc="title"/> @@ -137,14 +138,14 @@