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 29692992341..a163094b928 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -60,7 +60,7 @@ - + diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 4f3b5f43945..0f52361ce14 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -956,7 +956,6 @@ - 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 11db5214975..c6c238c92b6 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/base_import_module/__openerp__.py b/addons/base_import_module/__openerp__.py index 09ebca9cf41..262e1232278 100644 --- a/addons/base_import_module/__openerp__.py +++ b/addons/base_import_module/__openerp__.py @@ -13,7 +13,7 @@ for customization purpose. 'depends': ['web'], 'installable': True, 'auto_install': False, - 'data': [], + 'data': ['views/base_import_module.xml'], 'qweb': [], 'test': [], } diff --git a/addons/base_import_module/controllers/main.py b/addons/base_import_module/controllers/main.py index cbfbd75894b..d97e75ba8b6 100644 --- a/addons/base_import_module/controllers/main.py +++ b/addons/base_import_module/controllers/main.py @@ -1,14 +1,8 @@ # -*- coding: utf-8 -*- import functools -import os -import zipfile -from os.path import join as opj - import openerp from openerp.http import Controller, route, request, Response -MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes - def webservice(f): @functools.wraps(f) def wrap(*args, **kw): @@ -42,32 +36,4 @@ class ImportModule(Controller): @webservice def upload(self, mod_file=None, **kw): self.check_user() - imm = request.registry['ir.module.module'] - - if not mod_file: - raise Exception("No file sent.") - if not zipfile.is_zipfile(mod_file): - raise Exception("Not a zipfile.") - - success = [] - errors = dict() - with zipfile.ZipFile(mod_file, "r") as z: - for zf in z.filelist: - if zf.file_size > MAX_FILE_SIZE: - raise Exception("File '%s' exceed maximum allowed file size" % zf.filename) - - with openerp.tools.osutil.tempdir() as module_dir: - z.extractall(module_dir) - dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))] - for mod_name in dirs: - try: - # assert mod_name.startswith('theme_') - path = opj(module_dir, mod_name) - imm.import_module(request.cr, request.uid, mod_name, path, context=request.context) - success.append(mod_name) - except Exception, e: - errors[mod_name] = str(e) - r = ["Successfully imported module '%s'" % mod for mod in success] - for mod, error in errors.items(): - r.append("Error while importing module '%s': %r" % (mod, error)) - return '\n'.join(r) + return request.registry['ir.module.module'].import_zipfile(request.cr, request.uid, mod_file, context=request.context)[0] diff --git a/addons/base_import_module/models/__init__.py b/addons/base_import_module/models/__init__.py index a28c82fe5ba..02b8831509a 100644 --- a/addons/base_import_module/models/__init__.py +++ b/addons/base_import_module/models/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- import ir_module +import base_import_module diff --git a/addons/base_import_module/models/base_import_module.py b/addons/base_import_module/models/base_import_module.py new file mode 100644 index 00000000000..36edb5a56f3 --- /dev/null +++ b/addons/base_import_module/models/base_import_module.py @@ -0,0 +1,53 @@ +import base64 +from StringIO import StringIO +from io import BytesIO +from openerp.osv import osv, fields + +class base_import_module(osv.TransientModel): + """ Import Module """ + _name = "base.import.module" + _description = "Import Module" + + _columns = { + 'module_file': fields.binary('Module .ZIP file', required=True), + 'state':fields.selection([('init','init'),('done','done')], 'Status', readonly=True), + 'import_message': fields.char('Import message'), + } + + _defaults = { + 'state': 'init', + } + + def import_module(self, cr, uid, ids, context=None): + module_obj = self.pool.get('ir.module.module') + data = self.browse(cr, uid, ids[0] , context=context) + zip_data = base64.decodestring(data.module_file) + fp = BytesIO() + fp.write(zip_data) + res = module_obj.import_zipfile(cr, uid, fp, context=context) + self.write(cr, uid, ids, {'state': 'done', 'import_message': res[0]}, context=context) + context = dict(context, module_name=res[1]) + # Return wizard otherwise it will close wizard and will not show result message to user. + return { + 'name': 'Import Module', + 'view_type': 'form', + 'view_mode': 'form', + 'target': 'new', + 'res_id': ids[0], + 'res_model': 'base.import.module', + 'type': 'ir.actions.act_window', + 'context': context, + } + + def action_module_open(self, cr, uid, ids, context): + return { + 'domain': [('name', 'in', context.get('module_name',[]))], + 'name': 'Modules', + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'ir.module.module', + 'view_id': False, + 'type': 'ir.actions.act_window', + } + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index 18e60f858da..2a0a9ea7fed 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -1,14 +1,18 @@ import logging import os import sys +import zipfile from os.path import join as opj import openerp from openerp.osv import osv from openerp.tools import convert_file +from openerp.tools.translate import _ _logger = logging.getLogger(__name__) +MAX_FILE_SIZE = 100 * 1024 * 1024 # in megabytes + class view(osv.osv): _inherit = "ir.module.module" @@ -22,7 +26,7 @@ class view(osv.osv): unmet_dependencies = set(terp['depends']).difference(known_mods_names.keys()) if unmet_dependencies: - raise Exception("Unmet module dependencies: %s" % ', '.join(unmet_dependencies)) + raise osv.except_osv(_('Error !'), _("Unmet module dependencies: %s" % ', '.join(unmet_dependencies))) if mod: self.write(cr, uid, mod.id, values) @@ -69,3 +73,33 @@ class view(osv.osv): return True + def import_zipfile(self, cr, uid, module_file, context=None): + if not module_file: + raise Exception("No file sent.") + if not zipfile.is_zipfile(module_file): + raise osv.except_osv(_('Error !'), _('File is not a zip file!')) + + success = [] + errors = dict() + module_names = [] + with zipfile.ZipFile(module_file, "r") as z: + for zf in z.filelist: + if zf.file_size > MAX_FILE_SIZE: + raise osv.except_osv(_('Error !'), _("File '%s' exceed maximum allowed file size" % zf.filename)) + + with openerp.tools.osutil.tempdir() as module_dir: + z.extractall(module_dir) + dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))] + for mod_name in dirs: + module_names.append(mod_name) + try: + # assert mod_name.startswith('theme_') + path = opj(module_dir, mod_name) + self.import_module(cr, uid, mod_name, path, context=context) + success.append(mod_name) + except Exception, e: + errors[mod_name] = str(e) + r = ["Successfully imported module '%s'" % mod for mod in success] + for mod, error in errors.items(): + r.append("Error while importing module '%s': %r" % (mod, error)) + return '\n'.join(r), module_names diff --git a/openerp/addons/base/module/wizard/base_module_import_view.xml b/addons/base_import_module/views/base_import_module.xml similarity index 55% rename from openerp/addons/base/module/wizard/base_module_import_view.xml rename to addons/base_import_module/views/base_import_module.xml index d9d6bb6ee78..daaa7779c27 100644 --- a/openerp/addons/base/module/wizard/base_module_import_view.xml +++ b/addons/base_import_module/views/base_import_module.xml @@ -3,49 +3,51 @@ - Module Import - base.module.import + Import Module + base.import.module
- + -
-
+
+
- Module Import + Import Module ir.actions.act_window - base.module.import + base.import.module form form new - + sequence="100"/> + 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 c53168318a1..4cc0c9a6dd3 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 b2d71b6223e..863950b567b 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -276,7 +276,7 @@
- + updated document @@ -296,7 +296,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/payment/models/res_config.py b/addons/payment/models/res_config.py index 70668a296da..a74e595b52e 100644 --- a/addons/payment/models/res_config.py +++ b/addons/payment/models/res_config.py @@ -16,4 +16,7 @@ class AccountPaymentConfig(osv.TransientModel): 'module_payment_adyen': fields.boolean( 'Manage Payments Using Adyen', help='-It installs the module payment_adyen.'), + 'module_payment_buckaroo': fields.boolean( + 'Manage Payments Using Buckaroo', + help='-It installs the module payment_buckaroo.'), } diff --git a/addons/payment/views/res_config_view.xml b/addons/payment/views/res_config_view.xml index 101acb7c9ee..45c0a3d168e 100644 --- a/addons/payment/views/res_config_view.xml +++ b/addons/payment/views/res_config_view.xml @@ -20,6 +20,10 @@
+
+ +
diff --git a/addons/payment_adyen/views/adyen.xml b/addons/payment_adyen/views/adyen.xml index 75e166f92fe..79281f43a99 100644 --- a/addons/payment_adyen/views/adyen.xml +++ b/addons/payment_adyen/views/adyen.xml @@ -20,7 +20,7 @@ -
-
+
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 b57ddbac345..1e9a27f0b4f 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(); }, }); @@ -3394,6 +3394,12 @@ instance.web.form.CompletionFieldMixin = { classname: 'oe_m2o_dropdown_option' }); } + else if (values.length == 0) + values.push({ + label: _t("No results to show..."), + action: function() {}, + classname: 'oe_m2o_dropdown_option' + }); return values; }); @@ -4457,7 +4463,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ else return $.when(); }).done(function () { - if (!self.o2m.options.reload_on_button) { + var ds = self.o2m.dataset; + var cached_records = _.any([ds.to_create, ds.to_delete, ds.to_write], function(value) { + return value.length; + }); + if (!self.o2m.options.reload_on_button && !cached_records) { self.handle_button(name, id, callback); }else { self.handle_button(name, id, function(){ diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 5d6fe8b143f..bd3e8636e8c 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 4b951a8446f..446080875a6 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -108,8 +108,11 @@ class Website(openerp.addons.web.controllers.main.Home): locs = request.website.enumerate_pages() while True: start = pages * LOC_PER_SITEMAP - loc_slice = islice(locs, start, start + LOC_PER_SITEMAP) - urls = iuv.render(cr, uid, 'website.sitemap_locs', dict(locs=loc_slice), context=context) + values = { + 'locs': islice(locs, start, start + LOC_PER_SITEMAP), + 'url_root': request.httprequest.url_root[:-1], + } + urls = iuv.render(cr, uid, 'website.sitemap_locs', values, context=context) if urls.strip(): page = iuv.render(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context) if not first_page: @@ -155,22 +158,25 @@ class Website(openerp.addons.web.controllers.main.Home): @http.route('/website/theme_change', type='http', auth="user", website=True) def theme_change(self, theme_id=False, **kwargs): imd = request.registry['ir.model.data'] - view = request.registry['ir.ui.view'] + Views = request.registry['ir.ui.view'] - view_model, view_option_id = imd.get_object_reference( + _, theme_template_id = imd.get_object_reference( request.cr, request.uid, 'website', 'theme') - views = view.search( - request.cr, request.uid, [('inherit_id', '=', view_option_id)], - context=request.context) - view.write(request.cr, request.uid, views, {'inherit_id': False}, - context=request.context) + views = Views.search(request.cr, request.uid, [ + ('inherit_id', '=', theme_template_id), + ('application', '=', 'enabled'), + ], context=request.context) + Views.write(request.cr, request.uid, views, { + 'application': 'disabled', + }, context=request.context) if theme_id: module, xml_id = theme_id.split('.') - view_model, view_id = imd.get_object_reference( + _, view_id = imd.get_object_reference( request.cr, request.uid, module, xml_id) - view.write(request.cr, request.uid, [view_id], - {'inherit_id': view_option_id}, context=request.context) + Views.write(request.cr, request.uid, [view_id], { + 'application': 'enabled' + }, context=request.context) return request.render('website.themes', {'theme_changed': True}) @@ -194,54 +200,45 @@ class Website(openerp.addons.web.controllers.main.Home): module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context) return request.redirect(redirect) - @http.route('/website/customize_template_toggle', type='json', auth='user', website=True) - def customize_template_set(self, view_id): - view_obj = request.registry.get("ir.ui.view") - view = view_obj.browse(request.cr, request.uid, int(view_id), - context=request.context) - if view.inherit_id: - value = False - else: - value = view.inherit_option_id and view.inherit_option_id.id or False - view_obj.write(request.cr, request.uid, [view_id], { - 'inherit_id': value - }, context=request.context) - return True - @http.route('/website/customize_template_get', type='json', auth='user', website=True) - def customize_template_get(self, xml_id, optional=True): + def customize_template_get(self, xml_id, full=False): + """ Lists the templates customizing ``xml_id``. By default, only + returns optional templates (which can be toggled on and off), if + ``full=True`` returns all templates customizing ``xml_id`` + """ imd = request.registry['ir.model.data'] view_model, view_theme_id = imd.get_object_reference( request.cr, request.uid, 'website', 'theme') - user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context) - group_ids = [g.id for g in user.groups_id] + user = request.registry['res.users']\ + .browse(request.cr, request.uid, request.uid, request.context) + user_groups = set(user.groups_id) - view = request.registry.get("ir.ui.view") - views = view._views_get(request.cr, request.uid, xml_id, context=request.context) - done = {} + views = request.registry["ir.ui.view"]\ + ._views_get(request.cr, request.uid, xml_id, context=request.context) + done = set() result = [] for v in views: - if v.groups_id and [g for g in v.groups_id if g.id not in group_ids]: + if not user_groups.issuperset(v.groups_id): continue - if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional: - if v.inherit_option_id.id not in done: + if full or (v.application != 'always' and v.inherit_id.id != view_theme_id): + if v.inherit_id not in done: result.append({ - 'name': v.inherit_option_id.name, + 'name': v.inherit_id.name, 'id': v.id, 'xml_id': v.xml_id, 'inherit_id': v.inherit_id.id, 'header': True, 'active': False }) - done[v.inherit_option_id.id] = True + done.add(v.inherit_id) result.append({ 'name': v.name, 'id': v.id, 'xml_id': v.xml_id, 'inherit_id': v.inherit_id.id, 'header': False, - 'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id) + 'active': v.application in ('always', 'enabled'), }) return result @@ -381,7 +378,7 @@ class Website(openerp.addons.web.controllers.main.Home): """ response = werkzeug.wrappers.Response() return request.registry['website']._image( - request.cr, request.uid, model, id, field, response) + request.cr, request.uid, model, id, field, response, max_width, max_height) #------------------------------------------------------ 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/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 9d89665c56f..398bbb6a87b 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -13,8 +13,6 @@ from openerp.osv import osv, fields class view(osv.osv): _inherit = "ir.ui.view" _columns = { - 'inherit_option_id': fields.many2one('ir.ui.view','Optional Inheritancy'), - 'inherited_option_ids': fields.one2many('ir.ui.view','inherit_option_id','Optional Inheritancies'), 'page': fields.boolean("Whether this view is a web page template (complete)"), 'website_meta_title': fields.char("Website meta title", size=70, translate=True), 'website_meta_description': fields.text("Website meta description", size=160, translate=True), @@ -24,25 +22,30 @@ class view(osv.osv): 'page': False, } + + def _view_obj(self, cr, uid, view_id, context=None): + if isinstance(view_id, basestring): + return self.pool['ir.model.data'].xmlid_to_object( + cr, uid, view_id, raise_if_not_found=True, context=context + ) + elif isinstance(view_id, (int, long)): + return self.browse(cr, uid, view_id, context=context) + + # assume it's already a view object (WTF?) + return view_id + # Returns all views (called and inherited) related to a view # Used by translation mechanism, SEO and optional templates - def _views_get(self, cr, uid, view, options=True, context=None, root=True, stack_result=None): - if not context: - context = {} - if not stack_result: - stack_result = [] - - def view_obj(view): - if isinstance(view, basestring): - mod_obj = self.pool.get("ir.model.data") - m, n = view.split('.') - view = mod_obj.get_object(cr, uid, m, n, context=context) - elif isinstance(view, (int, long)): - view = self.pool.get("ir.ui.view").browse(cr, uid, view, context=context) - return view + def _views_get(self, cr, uid, view_id, options=True, context=None, root=True): + """ For a given view ``view_id``, should return: + * the view itself + * all views inheriting from it, enabled or not + - but not the optional children of a non-enabled child + * all views called from it (via t-call) + """ try: - view = view_obj(view) + view = self._view_obj(cr, uid, view_id, context=context) except ValueError: # Shall we log that ? return [] @@ -55,19 +58,25 @@ class view(osv.osv): node = etree.fromstring(view.arch) for child in node.xpath("//t[@t-call]"): try: - call_view = view_obj(child.get('t-call')) + called_view = self._view_obj(cr, uid, child.get('t-call'), context=context) except ValueError: continue - if call_view not in result: - result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result) + if called_view not in result: + result += self._views_get(cr, uid, called_view, options=options, context=context) - todo = view.inherit_children_ids - if options: - todo += filter(lambda x: not x.inherit_id, view.inherited_option_ids) - # Keep options in a determinitic order whatever their enabled disabled status - todo.sort(lambda x,y:cmp(x.id,y.id)) - for child_view in todo: - for r in self._views_get(cr, uid, child_view, options=bool(child_view.inherit_id), context=context, root=False, stack_result=result): + extensions = view.inherit_children_ids + if not options: + # only active children + extensions = (v for v in view.inherit_children_ids + if v.application in ('always', 'enabled')) + + # Keep options in a deterministic order regardless of their applicability + for extension in sorted(extensions, key=lambda v: v.id): + for r in self._views_get( + cr, uid, extension, + # only return optional grandchildren if this child is enabled + options=extension.application in ('always', 'enabled'), + context=context, root=False): if r not in result: result.append(r) return result 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.ace.js b/addons/website/static/src/js/website.ace.js index a8af74bb52d..27e5cd60e62 100644 --- a/addons/website/static/src/js/website.ace.js +++ b/addons/website/static/src/js/website.ace.js @@ -97,7 +97,7 @@ var viewId = $(document.documentElement).data('view-xmlid'); openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': viewId, - 'optional': false, + 'full': true, }).then(function (views) { self.loadViews.call(self, views); self.open.call(self); diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 595df04a088..fcb957cafda 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -470,8 +470,14 @@ }); menu.on('click', 'a[data-action!=ace]', function (event) { var view_id = $(event.currentTarget).data('view-id'); - openerp.jsonRpc('/website/customize_template_toggle', 'call', { - 'view_id': view_id + return openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'ir.ui.view', + method: 'toggle', + args: [], + kwargs: { + ids: [parseInt(view_id, 10)], + context: website.get_context() + } }).then( function() { window.location.reload(); }); 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/themes.xml b/addons/website/views/themes.xml index 82f5105adcd..a2408133207 100644 --- a/addons/website/views/themes.xml +++ b/addons/website/views/themes.xml @@ -203,82 +203,82 @@ All Default Themes --> -