From 5b74bbe712a8a538f243072fa8ad5381bafa1984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Dec 2013 11:01:31 +0100 Subject: [PATCH 01/14] [CLEAN] mail_group: removed mail_post_access modification because it is not necessary anymore. It was probably added with the first implementation of the blog. bzr revid: tde@openerp.com-20131206100131-smjfud6f8dv5dy5l --- addons/mail/mail_group.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index d1d26886823..5a9f8751f2d 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -34,7 +34,6 @@ class mail_group(osv.Model): _mail_flat_thread = False _inherit = ['mail.thread'] _inherits = {'mail.alias': 'alias_id'} - _mail_post_access = 'read' def _get_image(self, cr, uid, ids, name, args, context=None): result = dict.fromkeys(ids, False) From 4eec5e416c7682eaa22a5b29da297f7d232cf539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Dec 2013 11:02:30 +0100 Subject: [PATCH 02/14] [IMP] mail: update: call to get_object updated bzr revid: tde@openerp.com-20131206100230-n4xxvskdbf3m0h1f --- addons/mail/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/update.py b/addons/mail/update.py index 3d07ce739ab..94286817648 100644 --- a/addons/mail/update.py +++ b/addons/mail/update.py @@ -94,7 +94,7 @@ class publisher_warranty_contract(osv.osv): # old behavior based on res.log; now on mail.message, that is not necessarily installed IMD = self.pool['ir.model.data'] user = self.pool['res.users'].browse(cr, SUPERUSER_ID, SUPERUSER_ID) - poster = IMD.get_object(cr, SUPERUSER_ID, 'mail', 'group_all_employees') + poster = IMD.get_object(cr, SUPERUSER_ID, 'mail', 'group_all_employees', context=context, check_existence_and_raise=False) if not poster.exists(): if not user.exists(): return True From 1e5432edecf88ac86d1d9c057c4aa10112e59f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Dec 2013 11:11:17 +0100 Subject: [PATCH 03/14] [FIX] mail: fixed subtype check broken by get_object refactoring, should be fixed now + updated other call to get_object bzr revid: tde@openerp.com-20131206101117-atlch4ea102a4gx0 --- addons/mail/mail_thread.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index aebb7dab949..adb097c9b2b 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -165,7 +165,7 @@ class mail_thread(osv.AbstractModel): def read_followers_data(self, cr, uid, follower_ids, context=None): result = [] - technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one') + technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one', context=context, check_existence_and_raise=True) for follower in self.pool.get('res.partner').browse(cr, uid, follower_ids, context=context): is_editable = uid in map(lambda x: x.id, technical_group.users) is_uid = uid in map(lambda x: x.id, follower.user_ids) @@ -496,9 +496,9 @@ class mail_thread(osv.AbstractModel): posted = False for subtype in subtypes: - subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1], context=context) + subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1], context=context, check_existence_and_raise=False) if not subtype_rec.exists(): - _logger.debug('subtype %s not found, giving error "%s"' % (subtype, e)) + _logger.debug('subtype %s not found' % subtype) continue message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values) self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype, context=context) From 1037874e15061d4775214d02c3ade98ac46ecd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Dec 2013 11:17:16 +0100 Subject: [PATCH 04/14] [CLEAN] mail: removed modifications that have nothing to do with website in mail.js + typo introduced in a test file bzr revid: tde@openerp.com-20131206101716-08q6f4ufhj50e239 --- addons/email_template/wizard/mail_compose_message.py | 5 ++--- addons/mail/static/src/js/mail.js | 1 + addons/mail/tests/common.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index ac2f79b56f1..4c1e19abbe9 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -42,12 +42,11 @@ class mail_compose_message(osv.TransientModel): _inherit = 'mail.compose.message' def default_get(self, cr, uid, fields, context=None): - """ - """ + """ Override to pre-fill the data when having a template in single-email mode """ if context is None: context = {} res = super(mail_compose_message, self).default_get(cr, uid, fields, context=context) - if context.get('default_template_id'): + if res.get('composition_mode') != 'mass_mail' and context.get('default_template_id'): res.update( self.onchange_template_id( cr, uid, [], context['default_template_id'], res.get('composition_mode'), diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index b2d5e7e7f0c..269187df8cd 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -725,6 +725,7 @@ openerp.mail = function (session) { var values = { 'body': this.$('textarea').val(), 'subject': false, + 'parent_id': this.context.default_parent_id, 'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}), 'partner_ids': partner_ids, 'context': _.extend(this.parent_thread.context, { diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index b7a5a510409..68a329e043a 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Business Applications -# Copyright (c) 20123TODAY OpenERP S.A. +# Copyright (c) 2012-TODAY OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as From d10f4710194fd390a5849b1c414226be722fd9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Dec 2013 13:14:32 +0100 Subject: [PATCH 05/14] [FIX] [TESTS] mail, portal: fixed tests, because the error raised by the OROM has changed in some cases. bzr revid: tde@openerp.com-20131206121432-mz2jpj535tmcmftl --- addons/mail/tests/test_mail_group.py | 5 +++-- addons/mail/tests/test_mail_message.py | 13 +++++++------ addons/portal/tests/test_portal.py | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/addons/mail/tests/test_mail_group.py b/addons/mail/tests/test_mail_group.py index c1512441b13..34c7f0c00e0 100644 --- a/addons/mail/tests/test_mail_group.py +++ b/addons/mail/tests/test_mail_group.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.mail.tests.common import TestMail +from openerp.exceptions import AccessError from openerp.osv.orm import except_orm from openerp.tools import mute_logger @@ -40,7 +41,7 @@ class TestMailGroup(TestMail): self.mail_group.read(cr, user_employee_id, [self.group_pigs_id]) # Do: Bert creates a group -> ko, no access rights - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.mail_group.create(cr, user_noone_id, {'name': 'Test'}) # Do: Raoul creates a restricted group -> ok new_group_id = self.mail_group.create(cr, user_employee_id, {'name': 'Test'}) @@ -58,7 +59,7 @@ class TestMailGroup(TestMail): # Do: Raoul write on Jobs -> ok self.mail_group.write(cr, user_employee_id, [self.group_priv_id], {'name': 'modified'}) # Do: Bert cannot write on Private -> ko (read but no write) - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.mail_group.write(cr, user_noone_id, [self.group_priv_id], {'name': 're-modified'}) # Test: Bert cannot unlink the group with self.assertRaises(except_orm): diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py index 5dc5e9e247d..180661a0707 100644 --- a/addons/mail/tests/test_mail_message.py +++ b/addons/mail/tests/test_mail_message.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.mail.tests.common import TestMail +from openerp.exceptions import AccessError from openerp.osv.orm import except_orm from openerp.tools import mute_logger @@ -258,13 +259,13 @@ class TestMailMessage(TestMail): # ---------------------------------------- # Do: Bert creates a message on Pigs -> ko, no creation rights - self.assertRaises(except_orm, self.mail_message.create, + self.assertRaises(AccessError, self.mail_message.create, cr, user_bert_id, {'model': 'mail.group', 'res_id': self.group_pigs_id, 'body': 'Test'}) # Do: Bert create a message on Jobs -> ko, no creation rights - self.assertRaises(except_orm, self.mail_message.create, + self.assertRaises(AccessError, self.mail_message.create, cr, user_bert_id, {'model': 'mail.group', 'res_id': self.group_jobs_id, 'body': 'Test'}) # Do: Bert create a private message -> ko, no creation rights - self.assertRaises(except_orm, self.mail_message.create, + self.assertRaises(AccessError, self.mail_message.create, cr, user_bert_id, {'body': 'Test'}) # Do: Raoul creates a message on Jobs -> ok, write access to the related document @@ -410,15 +411,15 @@ class TestMailMessage(TestMail): for message in bert_jobs.message_ids: trigger_read = message.subject for partner in bert_jobs.message_follower_ids: - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): trigger_read = partner.name # Do: Bert comments Jobs, ko because no creation right - self.assertRaises(except_orm, + self.assertRaises(AccessError, self.mail_group.message_post, cr, user_bert_id, self.group_jobs_id, body='I love Pigs') # Do: Bert writes on its own profile, ko because no message create access - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.res_users.message_post(cr, user_bert_id, user_bert_id, body='I love Bert') self.res_partner.message_post(cr, user_bert_id, partner_bert_id, body='I love Bert') diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index 30e3004be0b..66cb0958774 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.mail.tests.common import TestMail +from openerp.exceptions import AccessError from openerp.osv.orm import except_orm from openerp.tools.misc import mute_logger @@ -63,7 +64,7 @@ class test_portal(TestMail): trigger_read = chell_pigs.name # Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message') # Do: Chell is added into Pigs followers and browse it -> ok for messages, ko for partners (no read permission) From ef8322273e29779aaa7054239c590910777b0b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Dec 2013 13:57:51 +0100 Subject: [PATCH 06/14] [FIX] [TESTS] project, hr_holidays and portal-related modules: fixed tests, because the raised errors have been updated bzr revid: tde@openerp.com-20131206125751-lx5ki1nz10pdo63r --- .../hr_holidays/tests/test_holidays_flow.py | 4 ++-- .../tests/test_access_rights.py | 21 ++++++++++--------- .../tests/test_access_rights.py | 11 +++++----- addons/project/tests/test_project_flow.py | 4 ++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py index 9b678e426d0..5a5d51a098f 100644 --- a/addons/hr_holidays/tests/test_holidays_flow.py +++ b/addons/hr_holidays/tests/test_holidays_flow.py @@ -23,7 +23,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta from openerp.addons.hr_holidays.tests.common import TestHrHolidaysBase -from openerp.exceptions import Warning +from openerp.exceptions import Warning, AccessError from openerp.osv.orm import except_orm from openerp.tools import mute_logger @@ -46,7 +46,7 @@ class TestHolidaysFlow(TestHrHolidaysBase): 'hr_holidays: wrong type days computation') # HrUser creates some holiday statuses -> crash because only HrManagers should do this - with self.assertRaises(except_orm): + with self.assertRaises(AccessError): self.holidays_status_dummy = self.hr_holidays_status.create(cr, self.user_hruser_id, { 'name': 'UserCheats', 'limit': True, diff --git a/addons/portal_project/tests/test_access_rights.py b/addons/portal_project/tests/test_access_rights.py index d536b5891e2..5b389df809f 100644 --- a/addons/portal_project/tests/test_access_rights.py +++ b/addons/portal_project/tests/test_access_rights.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.project.tests.test_project_base import TestProjectBase +from openerp.exceptions import AccessError from openerp.osv.orm import except_orm from openerp.tools import mute_logger @@ -93,13 +94,13 @@ class TestPortalProject(TestPortalProjectBase): self.project_task.write(cr, self.user_projectuser_id, task_ids, {'description': 'TestDescription'}) # Do: Bert reads project -> crash, no group - self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) + self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) # Test: no project task visible - self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) + self.assertRaises(AccessError, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) # Test: no project task readable - self.assertRaises(except_orm, self.project_task.read, cr, self.user_none_id, task_ids, ['name']) + self.assertRaises(AccessError, self.project_task.read, cr, self.user_none_id, task_ids, ['name']) # Test: no project task writable - self.assertRaises(except_orm, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'}) + self.assertRaises(AccessError, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'}) # Do: Chell reads project -> ok (portal ok public) self.project_project.read(cr, self.user_portal_id, pigs_id, ['name']) @@ -110,7 +111,7 @@ class TestPortalProject(TestPortalProjectBase): # Test: all project tasks readable self.project_task.read(cr, self.user_portal_id, task_ids, ['name']) # Test: no project task writable - self.assertRaises(except_orm, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'}) + self.assertRaises(AccessError, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'}) # Do: Donovan reads project -> ok (public) self.project_project.read(cr, self.user_public_id, pigs_id, ['name']) @@ -121,7 +122,7 @@ class TestPortalProject(TestPortalProjectBase): # Test: all project tasks readable self.project_task.read(cr, self.user_public_id, task_ids, ['name']) # Test: no project task writable - self.assertRaises(except_orm, self.project_task.write, cr, self.user_public_id, task_ids, {'description': 'TestDescription'}) + self.assertRaises(AccessError, self.project_task.write, cr, self.user_public_id, task_ids, {'description': 'TestDescription'}) # ---------------------------------------- # CASE2: portal project @@ -136,9 +137,9 @@ class TestPortalProject(TestPortalProjectBase): 'access rights: project user cannot see all tasks of a portal project') # Do: Bert reads project -> crash, no group - self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) + self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) # Test: no project task searchable - self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) + self.assertRaises(AccessError, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) # Data: task follower self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id]) @@ -174,7 +175,7 @@ class TestPortalProject(TestPortalProjectBase): 'access rights: project user cannot see all tasks of an employees project') # Do: Bert reads project -> crash, no group - self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) + self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) # Do: Chell reads project -> ko (portal ko employee) self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name']) @@ -202,7 +203,7 @@ class TestPortalProject(TestPortalProjectBase): 'access rights: employee user should not see tasks of a not-followed followers project, only assigned') # Do: Bert reads project -> crash, no group - self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) + self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name']) # Do: Chell reads project -> ko (portal ko employee) self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name']) diff --git a/addons/portal_project_issue/tests/test_access_rights.py b/addons/portal_project_issue/tests/test_access_rights.py index f5060ccc1ff..eaa06a69538 100644 --- a/addons/portal_project_issue/tests/test_access_rights.py +++ b/addons/portal_project_issue/tests/test_access_rights.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.portal_project.tests.test_access_rights import TestPortalProjectBase +from openerp.exceptions import AccessError from openerp.osv.orm import except_orm from openerp.tools import mute_logger @@ -71,11 +72,11 @@ class TestPortalIssue(TestPortalProjectBase): # Do: Bert reads project -> crash, no group # Test: no project issue visible - self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) + self.assertRaises(AccessError, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) # Test: no project issue readable - self.assertRaises(except_orm, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name']) + self.assertRaises(AccessError, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name']) # Test: no project issue writable - self.assertRaises(except_orm, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'}) + self.assertRaises(AccessError, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'}) # Do: Chell reads project -> ok (portal ok public) # Test: all project issues visible @@ -85,7 +86,7 @@ class TestPortalIssue(TestPortalProjectBase): # Test: all project issues readable self.project_issue.read(cr, self.user_portal_id, issue_ids, ['name']) # Test: no project issue writable - self.assertRaises(except_orm, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'}) + self.assertRaises(AccessError, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'}) # Do: Donovan reads project -> ok (public ok public) # Test: all project issues visible @@ -106,7 +107,7 @@ class TestPortalIssue(TestPortalProjectBase): # Do: Bert reads project -> crash, no group # Test: no project issue searchable - self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) + self.assertRaises(AccessError, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)]) # Data: issue follower self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id]) diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py index a037c92d2ae..ecbe66a0cea 100644 --- a/addons/project/tests/test_project_flow.py +++ b/addons/project/tests/test_project_flow.py @@ -20,7 +20,7 @@ ############################################################################## from openerp.addons.project.tests.test_project_base import TestProjectBase -from openerp.osv.orm import except_orm +from openerp.exceptions import AccessError from openerp.tools import mute_logger @@ -57,7 +57,7 @@ class TestProjectFlow(TestProjectBase): cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id # ProjectUser: set project as template -> raise - self.assertRaises(except_orm, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id]) + self.assertRaises(AccessError, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id]) # Other tests are done using a ProjectManager project = self.project_project.browse(cr, user_projectmanager_id, project_pigs_id) From d0b813a0165b840f9fc45b0e0f647341e7c7b750 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Fri, 6 Dec 2013 14:30:03 +0100 Subject: [PATCH 07/14] [IMP] fix website_hr_recruitment, website_event, and create contact qweb widget bzr revid: chm@openerp.com-20131206133003-to4lbgoynvviifq5 --- addons/website/models/ir_qweb.py | 42 +++++++++++++++++++ addons/website/static/src/css/website.css | 7 ++++ addons/website/static/src/css/website.sass | 6 +++ addons/website/views/website_templates.xml | 15 +++++++ addons/website_event/views/website_event.xml | 30 ++++--------- .../controllers/main.py | 26 +++++------- .../views/website_hr_recruitment.xml | 21 +++++----- 7 files changed, 100 insertions(+), 47 deletions(-) diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index bb2062bad92..d553acf363a 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -21,6 +21,7 @@ from lxml import etree, html from PIL import Image as I import openerp.modules +import openerp from openerp.osv import orm, fields from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT from openerp.addons.web.http import request @@ -197,6 +198,7 @@ class ManyToOne(orm.AbstractModel): matches = self.pool[column._obj].name_search( cr, uid, name=element.text_content().strip(), context=context) # FIXME: no match? More than 1 match? + print element.text_content().strip() assert len(matches) == 1 return matches[0][0] @@ -349,3 +351,43 @@ class RelativeDatetime(orm.AbstractModel): ] # get formatting from ir.qweb.field.relative but edition/save from datetime + + +class Contact(orm.AbstractModel): + _name = 'website.qweb.field.contact' + _inherit = ['website.qweb.field', 'website.qweb.field.many2one'] + + def from_html(self, cr, uid, model, column, element, context=None): + # FIXME: this behavior is really weird, what if the user wanted to edit the name of the related thingy? Should m2os really be editable without a widget? + divs = element.xpath(".//div") + for div in divs: + if div != divs[0]: + div.getparent().remove(div) + return super(Contact, self).from_html(cr, uid, model, column, element, context=context) + + def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None): + opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"] + + if not getattr(record, field_name): + return None + + id = getattr(record, field_name).id + field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context={"show_address": True}) + value = werkzeug.utils.escape( field_browse.name_get()[0][1] ) + + IMD = self.pool["ir.model.data"] + model, id = IMD.get_object_reference(cr, uid, "website", "contact") + view = self.pool["ir.ui.view"].browse(cr, uid, id, context=context) + + html = view.render({ + 'name': value.split("\n")[0], + 'address': werkzeug.utils.escape("\n".join(value.split("\n")[1:])), + 'phone': field_browse.phone, + 'mobile': field_browse.mobile, + 'fax': field_browse.fax, + 'email': field_browse.email, + 'fields': opf, + 'options': options + }, engine='website.qweb', context=context) + + return html diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index d62a3b9b371..5d0f452863f 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -496,3 +496,10 @@ table.well tr td { .oe_demo div a { color: white; } + +address .fa.fa-mobile-phone { + margin: 0 3px 0 2px; +} +address .fa.fa-file-text-o { + margin-right: 1px; +} diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index 8329ef09070..a08aee8cfd1 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -372,3 +372,9 @@ table.well tr font-weight: bold a color: white + +address + .fa.fa-mobile-phone + margin: 0 3px 0 2px + .fa.fa-file-text-o + margin-right: 1px diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 4ac6730ad1d..2ba72fc581e 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -686,5 +686,20 @@ Sitemap: sitemap.xml + diff --git a/addons/website_event/views/website_event.xml b/addons/website_event/views/website_event.xml index 619d4143864..884fbd6b235 100644 --- a/addons/website_event/views/website_event.xml +++ b/addons/website_event/views/website_event.xml @@ -336,19 +336,10 @@ -
- -
+
@@ -366,15 +357,10 @@

Organizer

-
- -
+
diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index 0c617524d94..c287f971ed8 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -17,7 +17,11 @@ class website_hr_recruitment(http.Controller): '/jobs/office/' ], type='http', auth="public", multilang=True) def jobs(self, department=None, office=None): - jobs = self._browse_jobs([]) + JobsObj = request.registry['hr.job'] + jobpost_ids = JobsObj.search(request.cr, request.uid, [], + order="website_published desc,no_of_recruitment desc", + context=request.context) + jobs = JobsObj.browse(request.cr, request.uid, jobpost_ids, context=dict(request.context, show_address=True, no_tag_br=True)) departments = set() offices = set() @@ -28,12 +32,12 @@ class website_hr_recruitment(http.Controller): offices.add(job.address_id) if department or office: - domain = [] - if department: - domain.append(('department_id','=', department.id)) - if office: - domain.append(('address_id','=', office.id)) - jobs = self._browse_jobs(domain) + _jobs = [] + for job in jobs: + if (department and job.department_id.id == department.id) or \ + (office and job.address_id.id == office.id): + _jobs.add(job) + jobs = _jobs return request.website.render("website_hr_recruitment.index", { 'jobs': jobs, @@ -97,11 +101,3 @@ class website_hr_recruitment(http.Controller): }, context=request.context) return request.redirect("/job/detail/%s/?enable_editor=1" % job_id) - - def _browse_jobs(self, domain): - Jobs = request.registry['hr.job'] - jobpost_ids = Jobs.search(request.cr, request.uid, domain, - order="website_published desc,no_of_recruitment desc", - context=request.context) - jobs = Jobs.browse(request.cr, request.uid, jobpost_ids, context=request.context) - return jobs diff --git a/addons/website_hr_recruitment/views/website_hr_recruitment.xml b/addons/website_hr_recruitment/views/website_hr_recruitment.xml index 40819142856..7d49f41782d 100644 --- a/addons/website_hr_recruitment/views/website_hr_recruitment.xml +++ b/addons/website_hr_recruitment/views/website_hr_recruitment.xml @@ -67,13 +67,12 @@ -
- - - - , - not published -
+ + not published
@@ -112,9 +111,11 @@

-

- , -
+
From 74ed3d2e5f2d0a3434bdd8c5704a079cdba59073 Mon Sep 17 00:00:00 2001 From: ddm Date: Fri, 6 Dec 2013 14:32:39 +0100 Subject: [PATCH 08/14] [FIX] Make tour declarative to enable autotests bzr revid: ddm@openerp.com-20131206133239-mrhduf2fi0sm8sh7 --- .../static/src/js/website.snippets.editor.js | 51 +++--- .../static/src/js/website.tour.banner.js | 60 +++---- addons/website/static/src/js/website.tour.js | 155 ++++++++++++------ addons/website_blog/controllers/main.py | 2 +- .../static/src/js/website.tour.blog.js | 74 +++------ .../static/src/js/website.tour.event.js | 68 ++------ .../static/src/js/website.tour.shop.js | 32 +--- 7 files changed, 205 insertions(+), 237 deletions(-) diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 22582ecc9e3..b93d1c2c761 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -207,17 +207,17 @@ self.make_active($target); }); }, - snippet_blur: function ($snipped_id) { - if ($snipped_id) { - if ($snipped_id.data("snippet-editor")) { - $snipped_id.data("snippet-editor").onBlur(); + snippet_blur: function ($snippet) { + if ($snippet) { + if ($snippet.data("snippet-editor")) { + $snippet.data("snippet-editor").onBlur(); } } }, - snippet_focus: function ($snipped_id) { - if ($snipped_id) { - if ($snipped_id.data("snippet-editor")) { - $snipped_id.data("snippet-editor").onFocus(); + snippet_focus: function ($snippet) { + if ($snippet) { + if ($snippet.data("snippet-editor")) { + $snippet.data("snippet-editor").onFocus(); } } }, @@ -230,31 +230,34 @@ } } }, - make_active: function ($snipped_id) { - if ($snipped_id && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snipped_id.get(0)) { + make_active: function ($snippet) { + if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) { return; } if (this.$active_snipped_id) { this.snippet_blur(this.$active_snipped_id); this.$active_snipped_id = false; } - if ($snipped_id && $snipped_id.length) { - if(_.indexOf(this.snippets, $snipped_id.get(0)) === -1) { - this.snippets.push($snipped_id.get(0)); + if ($snippet && $snippet.length) { + if(_.indexOf(this.snippets, $snippet.get(0)) === -1) { + this.snippets.push($snippet.get(0)); } - this.$active_snipped_id = $snipped_id; + this.$active_snipped_id = $snippet; this.create_overlay(this.$active_snipped_id); - this.snippet_focus($snipped_id); + this.snippet_focus($snippet); + } + if (_.isFunction($snippet.trigger)) { + $snippet.trigger('tour:snippet_activated'); } }, - create_overlay: function ($snipped_id) { - if (typeof $snipped_id.data("snippet-editor") === 'undefined') { - var $targets = this.activate_overlay_zones($snipped_id); + create_overlay: function ($snippet) { + if (typeof $snippet.data("snippet-editor") === 'undefined') { + var $targets = this.activate_overlay_zones($snippet); if (!$targets.length) return; - var editor = website.snippet.editorRegistry[$snipped_id.data("snippet-id")] || website.snippet.editorRegistry.resize; - $snipped_id.data("snippet-editor", new editor(this, $snipped_id)); + var editor = website.snippet.editorRegistry[$snippet.data("snippet-id")] || website.snippet.editorRegistry.resize; + $snippet.data("snippet-editor", new editor(this, $snippet)); } - this.cover_target($snipped_id.data('overlay'), $snipped_id); + this.cover_target($snippet.data('overlay'), $snippet); }, path_eval: function(path){ @@ -869,7 +872,7 @@ var editor = new Editor(self, self.$target, val['snippet-style-id']); $ul.prepend(editor.$el); }); - + if ($ul.find("li").length) { $styles.removeClass("hidden"); } @@ -1149,7 +1152,7 @@ } }, }); - + website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({ drop_and_build_snippet: function() { var id = $(".carousel").length; @@ -1224,7 +1227,7 @@ var new_index = 0; var cycle = this.$inner.find('.item').length - 1; var index = this.$inner.find('.item.active').index(); - + if (cycle > 0) { this.remove_process = true; var $el = this.$inner.find('.item.active'); diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index 75838ada953..a60ac05a633 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -11,7 +11,7 @@ }); website.BannerTour = website.Tour.extend({ - id: 'banner-tutorial', + id: 'banner', name: "Insert a banner", startPath: '/page/website.homepage', init: function (editor) { @@ -19,11 +19,10 @@ self.steps = [ { stepId: 'welcome', - orphan: true, - backdrop: true, title: "Welcome to your website!", content: "This tutorial will guide you to build your home page. We will start by adding a banner.", template: self.popover({ next: "Start Tutorial", end: "Skip It" }), + backdrop: true, }, { stepId: 'edit-page', @@ -31,8 +30,10 @@ placement: 'bottom', title: "Edit this page", content: "Every page of your website can be modified through the Edit button.", - triggers: function () { - editor.on('tour:editor_bar_loaded', self, self.moveToNextStep); + trigger: { + emitter: editor, + type: 'openerp', + id: 'tour:editor_bar_loaded', }, }, { @@ -41,11 +42,7 @@ placement: 'bottom', title: "Insert building blocks", content: "To add content in a page, you can insert building blocks.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - } + trigger: 'click', }, { stepId: 'drag-banner', @@ -53,9 +50,7 @@ placement: 'bottom', title: "Drag & Drop a Banner", content: "Drag the Banner block and drop it in your page.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, + trigger: 'drag', }, { stepId: 'edit-title', @@ -64,7 +59,7 @@ title: "Customize banner's text", content: "Click in the text and start editing it. Click continue once it's done.", template: self.popover({ next: "Continue" }), - triggers: function () { + onStart: function () { var $banner = $("#wrap [data-snippet-id=carousel]:first"); if ($banner.length) { $banner.click(); @@ -85,11 +80,7 @@ placement: 'bottom', title: "Add Another Block", content: "Let's add another building block to your page.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - } + trigger: 'click', }, { stepId: 'drag-three-columns', @@ -97,9 +88,7 @@ placement: 'bottom', title: "Drag & Drop a Block", content: "Drag the '3 Columns' block and drop it below the banner.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, + trigger: 'drag', }, { stepId: 'activate-text-block-title', @@ -107,31 +96,28 @@ placement: 'top', title: "Edit an Area", content: "Select any area of the page to modify it. Click on this subtitle.", - triggers: function () { - $('#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]').one('click', function () { - self.moveToNextStep(); - }); + trigger: { + id: 'tour:snippet_activated' }, }, { stepId: 'remove-text-block-title', element: '.ui-draggable.oe_active .oe_snippet_remove', placement: 'top', - reflex: true, title: "Delete the Title", content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the trash can to delete the title.", + trigger: 'click', }, { stepId: 'save-changes', element: 'button[data-action=save]', placement: 'right', - reflex: true, title: "Save your modifications", content: "Publish your page by clicking on the 'Save' button.", + trigger: 'click', }, { stepId: 'part-2', - orphan: true, title: "Congratulation!", content: "Your homepage has been updated.", template: self.popover({ next: "Continue" }), @@ -142,35 +128,31 @@ placement: 'bottom', title: "Test Your Mobile Version", content: "Let's check how your homepage looks like on mobile devices.", - triggers: function () { - $(document).one('shown.bs.modal', function () { - self.moveToNextStep(); - }); - } + trigger: { + emitter: $(document), + id: 'shown.bs.modal', + }, }, { stepId: 'show-mobile-close', element: 'button[data-dismiss=modal]', placement: 'right', - reflex: true, title: "Close Mobile Preview", content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.", + trigger: 'click', }, { stepId: 'show-tutorials', element: '#help-menu-button', placement: 'left', - reflex: true, title: "More Tutorials", content: "Get more tutorials through this 'Help' menu or click on the left 'Edit' button to continue modifying this page.", template: self.popover({ end: "Close Tutorial" }), + trigger: 'click', } ]; return this._super(); }, - resume: function () { - return this.isCurrentStep('part-2') && this._super(); - }, }); }()); diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index a14a55bded5..d028f15afdd 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -14,49 +14,76 @@ storage: this.tourStorage, keyboard: false, template: this.popover(), + onHide: function () { + window.scrollTo(0, 0); + } }); this.registerSteps(); }, registerSteps: function () { var self = this; this.tour.addSteps(_.map(this.steps, function (step) { - step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title }); - if (step.modal) { - step.onShow = function () { - var $doc = $(document); - function onStop () { - if (step.modal.stopOnClose) { - self.stop(); - } - } - $doc.on('hide.bs.modal', onStop); - $doc.one('shown.bs.modal', function () { - $('.modal button.btn-primary').one('click', function () { - $doc.off('hide.bs.modal', onStop); - self.moveToStep(step.modal.afterSubmit); + step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title }); + if (!step.element) { + step.orphan = true; + } + if (step.trigger) { + if (step.trigger === 'click') { + step.triggers = function (callback) { + $(step.element).one('click', function () { + (callback || self.moveToNextStep).call(self); }); - self.moveToNextStep(); - }); + }; + } else if (step.trigger === 'drag') { + step.triggers = function (callback) { + self.onSnippetDragged(callback || self.moveToNextStep); + }; + } else if (step.trigger && step.trigger.id) { + if (step.trigger.emitter && step.trigger.type === 'openerp') { + step.triggers = function (callback) { + step.trigger.emitter.on(step.trigger.id, self, function customHandler () { + step.trigger.emitter.off(step.trigger.id, customHandler); + (callback || self.moveToNextStep).apply(self, arguments); + }); + }; + } else { + step.triggers = function (callback) { + var emitter = step.trigger.emitter || $(step.element); + emitter.one(step.trigger.id, function customHandler () { + (callback || self.moveToNextStep).apply(self, arguments); + }); + }; + } + } else if (step.trigger.modal) { + step.triggers = function (callback) { + var $doc = $(document); + function onStop () { + if (step.trigger.modal.stopOnClose) { + self.stop(); + } + } + $doc.on('hide.bs.modal', onStop); + $doc.one('shown.bs.modal', function () { + $('.modal button.btn-primary').one('click', function () { + $doc.off('hide.bs.modal', onStop); + self.moveToStep(step.trigger.modal.afterSubmit); + }); + (callback || self.moveToNextStep).call(self); + }); + }; + } + } + step.onShow = (function () { + var executed = false; + return function () { + if (!executed) { + _.isFunction(step.onStart) && step.onStart(); + _.isFunction(step.triggers) && step.triggers(); + executed = true; + } }; - } else if (step.triggers) { - step.onShow = (function () { - var executed = false; - return function () { - if (!executed) { - step.triggers(); - executed = true; - } - }; - }()); - } else if (step.reflex) { - step.onShow = function () { - $(step.element).one('click', function () { - self.moveToNextStep(); - }); - }; - delete step.reflex; - } - return step; + }()); + return step; })); }, reset: function () { @@ -108,7 +135,7 @@ redirect: function (url) { url = url || new website.UrlParser(window.location.href); var path = (this.startPath && url.pathname !== this.startPath) ? this.startPath : url.pathname; - var search = url.addToSearch(this.id + "=true"); + var search = url.activateTutorial(this.id); var newUrl = path + search; window.location.replace(newUrl); }, @@ -122,8 +149,7 @@ trigger: function (url) { // Override if necessary url = url || new website.UrlParser(window.location.href); - var urlTrigger = this.id + "=true"; - return url.search.indexOf(urlTrigger) >= 0; + return url.isActive(this.id); }, testUrl: function (pattern) { var url = new website.UrlParser(window.location.href); @@ -132,21 +158,22 @@ popover: function (options) { return openerp.qweb.render('website.tour_popover', options); }, - onSnippetDraggedAdvance: function (snippetId, stepId) { + onSnippetDragged: function (callback) { var self = this; function beginDrag () { $('.popover.tour').remove(); function advance () { - if (stepId) { - self.moveToStep(stepId); - } else { - self.moveToNextStep(); + if (_.isFunction(callback)) { + callback.call(self); } } $(document.body).one('mouseup', advance); } $('#website-top-navbar [data-snippet-id].ui-draggable').one('mousedown', beginDrag); }, + onSnippetDraggedAdvance: function () { + onSnippetDragged(self.moveToNextStep); + }, }); website.UrlParser = openerp.Class.extend({ @@ -162,21 +189,35 @@ this.origin = a.origin; this.search = a.search; this.hash = a.hash; - this.addToSearch= function (query) { + function generateTrigger (id) { + return "tutorial."+id+"=true"; + } + this.activateTutorial = function (id) { + var urlTrigger = generateTrigger(id); var querystring = _.filter(this.search.split('?'), function (str) { return str; }); if (querystring.length > 0) { - var queries = querystring[0].split("&"); - queries.push(query); - return "?" + _.uniq(queries).join("&"); + var queries = _.filter(querystring[0].split("&"), function (query) { + return query.indexOf("tutorial.") < 0 + }); + queries.push(urlTrigger); + return "?"+_.uniq(queries).join("&"); } else { - return "?"+query; + return "?"+urlTrigger; } }; + this.isActive = function (id) { + var urlTrigger = generateTrigger(id); + return this.search.indexOf(urlTrigger) >= 0; + }; }, }); + var TestConsole = website.TestConsole = { + tests: [] + }; + website.EditorBar.include({ tours: [], start: function () { @@ -202,6 +243,24 @@ }, registerTour: function (tour) { this.tours.push(tour); + TestConsole.tests.push({ + id: tour.id, + run: function runTest () { + var actionSteps = _.filter(tour.steps, function (step) { + return step.triggers; + }); + var currentIndex = 0; + function executeStep (step) { + var $element = $(step.element); + step.triggers(function () { + currentIndex = currentIndex + 1; + executeStep(actionSteps[currentIndex]); + }); + $element.click(); + } + executeStep(actionSteps[currentIndex]); + }, + }); }, }); diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py index 0e53809ae77..d38d110aac1 100644 --- a/addons/website_blog/controllers/main.py +++ b/addons/website_blog/controllers/main.py @@ -154,7 +154,7 @@ class WebsiteBlog(http.Controller): @website.route([ '/blogpost//', ], type='http', auth="public", multilang=True) - def blog_post(self, blog_post=None, tag=None, date=None, page=1, enable_editor=None): + def blog_post(self, blog_post, tag=None, date=None, page=1, enable_editor=None, **post): """ Prepare all values to display the blog. :param blog_post: blog post currently browsed. If not set, the user is diff --git a/addons/website_blog/static/src/js/website.tour.blog.js b/addons/website_blog/static/src/js/website.tour.blog.js index 2a7d3e69a47..989c4227a09 100644 --- a/addons/website_blog/static/src/js/website.tour.blog.js +++ b/addons/website_blog/static/src/js/website.tour.blog.js @@ -11,26 +11,25 @@ }); website.BlogTour = website.Tour.extend({ - id: 'blog-tutorial', + id: 'blog', name: "Create a blog post", init: function (editor) { var self = this; self.steps = [ { stepId: 'welcome-blog', - orphan: true, - backdrop: true, title: "New Blog Post", content: "Let's go through the first steps to write beautiful blog posts.", template: self.popover({ next: "Start Tutorial", end: "Skip" }), + backdrop: true, }, { stepId: 'content-menu', element: '#content-menu-button', placement: 'left', - reflex: true, title: "Add Content", content: "Create new pages, blogs, menu items and products through the 'Content' menu.", + trigger: 'click', }, { stepId: 'new-post-entry', @@ -38,9 +37,11 @@ placement: 'left', title: "New Blog Post", content: "Select this entry to create a new blog post.", - modal: { - stopOnClose: true, - afterSubmit: 'post-page', + trigger: { + modal: { + stopOnClose: true, + afterSubmit: 'post-page', + }, }, }, { @@ -49,15 +50,12 @@ placement: 'right', title: "Which Blog?", content: "Blog posts are organized in multiple categories (news, job offers, events, etc). Select News and click Continue.", - triggers: function () { - function newsSelected () { - var $this = $(this); - if ($this.find('[value='+$this.val()+']').text().toLowerCase() === 'news') { - self.moveToNextStep(); - $('.modal select').off('change', newsSelected); - } - } - $('.modal select').on('change', newsSelected); + trigger: { + emitter: $('.modal select'), + id: 'change', + predicate: function ($el, value) { + return $el.find('[value='+value+']').text().toLowerCase() === 'news'; + }, }, }, { @@ -69,7 +67,6 @@ }, { stepId: 'post-page', - orphan: true, title: "Blog Post Created", content: "This is your new blog post. We will edit your pages inline. What You See Is What You Get. No need for a complex backend.", template: self.popover({ next: "Continue" }), @@ -88,11 +85,7 @@ placement: 'bottom', title: "Layout Your Blog Post", content: "Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - }, + trigger: 'click', }, { stepId: 'drag-image-text', @@ -100,9 +93,7 @@ placement: 'bottom', title: "Drag & Drop a Block", content: "Drag the 'Image-Text' block and drop it in your page.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, + trigger: 'drag', }, { stepId: 'add-text-block', @@ -110,11 +101,7 @@ placement: 'bottom', title: "Add Another Block", content: "Let's add another block to your post.", - triggers: function () { - $('button[data-action=snippet]').on('click', function () { - self.moveToNextStep(); - }); - } + trigger: 'click', }, { stepId: 'drag-text-block', @@ -122,9 +109,7 @@ placement: 'bottom', title: "Drag & Drop a block", content: "Drag the 'Text Block' block and drop it below the image block.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, + trigger: 'drag', }, { stepId: 'activate-text-block-title', @@ -132,19 +117,15 @@ placement: 'top', title: "Edit an Area", content: "Select any area of the page to modify it. Click on this subtitle.", - triggers: function () { - $('#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]').one('click', function () { - self.moveToNextStep(); - }); - }, + trigger: 'click', }, { stepId: 'remove-text-block-title', element: '.oe_snippet_remove:last', placement: 'top', - reflex: true, title: "Delete the Title", content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the cross to delete the title.", + trigger: 'click', }, { stepId: 'save-changes', @@ -152,35 +133,26 @@ placement: 'right', title: "Save Your Blog", content: "Click the Save button to record changes on the page.", - triggers: function () { - var $publish = $('button.js_publish_btn'); - if ($publish.length > 0 && $publish.is(":visible")) { - self.moveToNextStep(); - } - }, + trigger: 'click', }, { stepId: 'publish-post', element: 'button.js_publish_btn', placement: 'top', - reflex: true, title: "Publish Your Post", content: "Your blog post is not yet published. You can update this draft version and publish it once you are ready.", + trigger: 'click', }, { stepId: 'end-tutorial', - orphan: true, - backdrop: true, title: "Thanks!", content: "This tutorial is finished. To discover more features, improve the content of this page and try the Promote button in the top right menu.", template: self.popover({ end: "Close Tutorial" }), + backdrop: true, }, ]; return this._super(); }, - resume: function () { - return (this.isCurrentStep('post-page') || this.isCurrentStep('save-changes')) && this._super(); - }, trigger: function () { return (this.resume() && this.testUrl(/^\/blogpost\/[0-9]+\//)) || this._super(); }, diff --git a/addons/website_event/static/src/js/website.tour.event.js b/addons/website_event/static/src/js/website.tour.event.js index 82728bf2ec9..b9a0c7045d2 100644 --- a/addons/website_event/static/src/js/website.tour.event.js +++ b/addons/website_event/static/src/js/website.tour.event.js @@ -11,26 +11,25 @@ }); website.EventTour = website.Tour.extend({ - id: 'event-tutorial', + id: 'event', name: "Create an event", init: function (editor) { var self = this; self.steps = [ { stepId: 'welcome-event', - orphan: true, - backdrop: true, title: "Create an Event", content: "Let's go through the firsts step to publish a new event.", template: self.popover({ next: "Start Tutorial", end: "Skip It" }), + backdrop: true, }, { stepId: 'content-menu', element: '#content-menu-button', placement: 'left', - reflex: true, title: "Add Content", content: "The Content menu allows to create new pages, events, menus, etc.", + trigger: 'click', }, { stepId: 'new-post-entry', @@ -38,9 +37,11 @@ placement: 'left', title: "New Event", content: "Click here to create a new event.", - modal: { - stopOnClose: true, - afterSubmit: 'event-page', + trigger: { + modal: { + stopOnClose: true, + afterSubmit: 'event-page', + } }, }, { @@ -52,7 +53,6 @@ }, { stepId: 'event-page', - orphan: true, title: "New Event Created", content: "This is your new event page. We will edit the event presentation page.", template: self.popover({ next: "OK" }), @@ -71,11 +71,7 @@ placement: 'bottom', title: "Layout your event", content: "Insert blocks like 'Banner' to layout the body of your event.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - }, + trigger: 'click', }, { stepId: 'drag-banner', @@ -83,9 +79,7 @@ placement: 'bottom', title: "Drag & Drop a block", content: "Drag the 'Banner' block and drop it in your page.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, + trigger: 'drag', }, { stepId: 'add-text-block', @@ -93,11 +87,7 @@ placement: 'bottom', title: "Layout your event", content: "Insert another block to your event.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - }, + trigger: 'click', }, { stepId: 'drag-text-block', @@ -105,12 +95,7 @@ placement: 'bottom', title: "Drag & Drop a block", content: "Drag the 'Text Block' block below the banner.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, - onHide: function () { - window.scrollTo(0, 0); - }, + trigger: 'drag', }, { stepId: 'add-three-columns', @@ -118,11 +103,7 @@ placement: 'bottom', title: "Layout your event", content: "Insert a last block to your event.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - }, + trigger: 'click', }, { stepId: 'drag-three-columns', @@ -130,12 +111,7 @@ placement: 'bottom', title: "Drag & Drop a block", content: "Drag the 'Three Columns' block at the bottom.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, - onHide: function () { - window.scrollTo(0, 0); - }, + trigger: 'drag', }, { stepId: 'save-changes', @@ -143,43 +119,35 @@ placement: 'right', title: "Save your modifications", content: "Once you click on save, your event is updated.", - triggers: function () { - var $publish = $('button.js_publish_btn'); - if ($publish.length > 0 && $publish.is(":visible")) { - self.moveToNextStep(); - } - }, + trigger: 'click', }, { stepId: 'publish-event', element: 'button.js_publish_btn', placement: 'top', - reflex: true, title: "Publish your event", content: "Click to publish your event.", + trigger: 'click', }, { stepId: 'customize-event', element: '.js_publish_management button:last', placement: 'left', - reflex: true, title: "Customize your event", content: "Click here to customize your event further.", + trigger: 'click', }, { stepId: 'edit-event-backend', element: '.js_publish_management ul>li>a', placement: 'left', - reflex: true, title: "Customize your event", content: "Click here to edit your event in the backend.", + trigger: 'click', }, ]; return this._super(); }, - resume: function () { - return (this.isCurrentStep('event-page') || this.isCurrentStep('save-changes')) && this._super(); - }, trigger: function () { return (this.resume() && this.testUrl(/^\/event\/[0-9]+\/register/)) || this._super(); }, diff --git a/addons/website_sale/static/src/js/website.tour.shop.js b/addons/website_sale/static/src/js/website.tour.shop.js index a389b06f9ff..93eb3732cc5 100644 --- a/addons/website_sale/static/src/js/website.tour.shop.js +++ b/addons/website_sale/static/src/js/website.tour.shop.js @@ -11,26 +11,25 @@ }); website.EditorShopTour = website.Tour.extend({ - id: 'shop-tutorial', + id: 'shop', name: "Create a product", init: function (editor) { var self = this; self.steps = [ { stepId: 'welcome-shop', - orphan: true, - backdrop: true, title: "Welcome to your shop", content: "You successfully installed the e-commerce. This guide will help you to create your product and promote your sales.", template: self.popover({ next: "Start Tutorial", end: "Skip It" }), + backdrop: true, }, { stepId: 'content-menu', element: '#content-menu-button', placement: 'left', - reflex: true, title: "Create your first product", content: "Click here to add a new product.", + trigger: 'click', }, { stepId: 'edit-entry', @@ -53,11 +52,10 @@ }, { stepId: 'product-page', - orphan: true, - backdrop: true, title: "New product created", content: "This page contains all the information related to the new product.", template: self.popover({ next: "OK" }), + backdrop: true, }, { stepId: 'edit-price', @@ -110,11 +108,7 @@ placement: 'bottom', title: "Describe the product for your audience", content: "Insert blocks like text-image, or gallery to fully describe the product and make your visitors want to buy this product.", - triggers: function () { - $('button[data-action=snippet]').one('click', function () { - self.moveToNextStep(); - }); - }, + trigger: 'click', }, { stepId: 'drag-big-picture', @@ -122,9 +116,7 @@ placement: 'bottom', title: "Drag & Drop a block", content: "Drag the 'Big Picture' block and drop it in your page.", - triggers: function () { - self.onSnippetDraggedAdvance(); - }, + trigger: 'drag', }, { stepId: 'save-changes', @@ -132,28 +124,20 @@ placement: 'right', title: "Save your modifications", content: "Once you click on save, your product is updated.", - triggers: function () { - var $publish = $('button.js_publish_btn'); - if ($publish.length > 0 && $publish.is(":visible")) { - self.moveToNextStep(); - } - }, + trigger: 'click', }, { stepId: 'publish-product', element: 'button.js_publish_btn', placement: 'top', - reflex: true, title: "Publish your product", content: "Click to publish your product so your customers can see it.", + trigger: 'click', }, ]; return this._super(); }, - resume: function () { - return (this.isCurrentStep('product-page') || this.isCurrentStep('save-changes')) && this._super(); - }, trigger: function () { return (this.resume() && this.testUrl(/^\/shop\/product\/[0-9]+\//)) || this._super(); }, From 9be1dbfe9ec4cde33c748daed67687102c869687 Mon Sep 17 00:00:00 2001 From: ddm Date: Fri, 6 Dec 2013 15:09:40 +0100 Subject: [PATCH 09/14] [FIX] Regression in shop tour bzr revid: ddm@openerp.com-20131206140940-v0rerf362fuj2tqz --- addons/website/static/src/js/website.tour.js | 4 ++-- .../website_sale/static/src/js/website.tour.shop.js | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index d028f15afdd..90f2f8a3d21 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -31,7 +31,7 @@ if (step.trigger === 'click') { step.triggers = function (callback) { $(step.element).one('click', function () { - (callback || self.moveToNextStep).call(self); + (callback || self.moveToNextStep).apply(self); }); }; } else if (step.trigger === 'drag') { @@ -68,7 +68,7 @@ $doc.off('hide.bs.modal', onStop); self.moveToStep(step.trigger.modal.afterSubmit); }); - (callback || self.moveToNextStep).call(self); + (callback || self.moveToNextStep).apply(self); }); }; } diff --git a/addons/website_sale/static/src/js/website.tour.shop.js b/addons/website_sale/static/src/js/website.tour.shop.js index 93eb3732cc5..c323e5185e4 100644 --- a/addons/website_sale/static/src/js/website.tour.shop.js +++ b/addons/website_sale/static/src/js/website.tour.shop.js @@ -37,9 +37,11 @@ placement: 'left', title: "Create a new product", content: "Select 'New Product' to create it and manage its properties to boost your sales.", - modal: { - stopOnClose: true, - afterSubmit: 'product-page', + trigger: { + modal: { + stopOnClose: true, + afterSubmit: 'product-page', + }, }, }, { @@ -48,7 +50,6 @@ placement: 'right', title: "Choose name", content: "Enter a name for your new product then click 'Continue'.", - }, { stepId: 'product-page', @@ -73,7 +74,7 @@ content: "Click here to set an image describing your product.", triggers: function () { function registerClick () { - $('button.image-edit-button').one('click', function () { + $('button.hover-edition-button').one('click', function () { $('#wrap img.img:first').off('hover', registerClick); self.moveToNextStep(); }); From 1071cdb1d3dc347d747458f9fcd51ef75d5a92d3 Mon Sep 17 00:00:00 2001 From: ddm Date: Fri, 6 Dec 2013 16:08:55 +0100 Subject: [PATCH 10/14] [IMP] Improve event support for tour and tests bzr revid: ddm@openerp.com-20131206150855-0jsw3i2lm2l3qant --- addons/website/static/src/js/website.snippets.editor.js | 8 +++++--- addons/website/static/src/js/website.tour.banner.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index b93d1c2c761..af2183b576c 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -26,7 +26,6 @@ self.snippets.$button.removeClass("hidden"); website.snippet.stop_animation(); website.snippet.start_animation(); - self.trigger('tour:editor_bar_loaded'); }); return this._super.apply(this, arguments); @@ -247,7 +246,7 @@ this.snippet_focus($snippet); } if (_.isFunction($snippet.trigger)) { - $snippet.trigger('tour:snippet_activated'); + $snippet.trigger('snippet-activated'); } }, create_overlay: function ($snippet) { @@ -384,7 +383,10 @@ snippet.drop_and_build_snippet($target); } } - setTimeout(function () {self.make_active($target);},0); + setTimeout(function () { + $snippet.trigger('snippet-dropped'); + self.make_active($target); + },0); } else { $toInsert.remove(); if (self.$modal.find('input:not(:checked)').length) { diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index a60ac05a633..70c5dd9dc38 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -33,7 +33,7 @@ trigger: { emitter: editor, type: 'openerp', - id: 'tour:editor_bar_loaded', + id: 'rte:ready', }, }, { @@ -97,7 +97,7 @@ title: "Edit an Area", content: "Select any area of the page to modify it. Click on this subtitle.", trigger: { - id: 'tour:snippet_activated' + id: 'snippet-activated' }, }, { From d4c38fc501e2e55f4ce8aa267a6287de862b270f Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Fri, 6 Dec 2013 16:47:59 +0100 Subject: [PATCH 11/14] [IMP] website snippet: change trigger of dropped and activate snippet for tour and runbot snippet tests bzr revid: chm@openerp.com-20131206154759-7t67lat285zrc2mk --- addons/website/static/src/js/website.snippets.editor.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index af2183b576c..53ea79fe907 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -245,9 +245,7 @@ this.create_overlay(this.$active_snipped_id); this.snippet_focus($snippet); } - if (_.isFunction($snippet.trigger)) { - $snippet.trigger('snippet-activated'); - } + $("#oe_snippets").trigger('snippet-activated', $snippet); }, create_overlay: function ($snippet) { if (typeof $snippet.data("snippet-editor") === 'undefined') { @@ -384,7 +382,7 @@ } } setTimeout(function () { - $snippet.trigger('snippet-dropped'); + $("#oe_snippets").trigger('snippet-dropped', $target); self.make_active($target); },0); } else { From 3f550a2e1184fb7e6d0de944e172e4576a0717d1 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Fri, 6 Dec 2013 17:31:22 +0100 Subject: [PATCH 12/14] [ADD] website: test.js bzr revid: chm@openerp.com-20131206163122-zuvttkq5o0ktlt85 --- .../static/src/js/website.snippets.editor.js | 1 + addons/website/static/src/js/website.tests.js | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 addons/website/static/src/js/website.tests.js diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 53ea79fe907..ef4c029280e 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -305,6 +305,7 @@ $toInsert = $snippet.find('.oe_snippet_body').clone(); $toInsert.removeClass('oe_snippet_body'); + $toInsert.data('src-snippet-id', snipped_id); if (!$toInsert.data('snippet-id')) { $toInsert.attr('data-snippet-id', snipped_id); } else { diff --git a/addons/website/static/src/js/website.tests.js b/addons/website/static/src/js/website.tests.js new file mode 100644 index 00000000000..977dc8e1848 --- /dev/null +++ b/addons/website/static/src/js/website.tests.js @@ -0,0 +1,40 @@ +var tests = {}; +var droptest = function () { + var errors = []; + for (var snippet_id in tests) { + if (!tests[snippet_id]['activated'] || !tests[snippet_id]['dropped']){ + console.log("Can't dropped or activated snippet: " + snippet_id); + } + } + if (errors.length) { + console.log(tests); + throw new Error("Can't dropped or activated at least one snippet"); + } + $("#oe_snippets").off('snippet-activated snippet-dropped'); +}; +var droptesttime = setTimeout(droptest,0); + +$("#oe_snippets").off('snippet-activated snippet-dropped') + .on('snippet-activated', function (event, dom) { + tests[$(dom).data('src-snippet-id')]['activated'] = true; + clearTimeout(droptesttime); + droptesttime = setTimeout(droptest,0); + }) + .on('snippet-dropped', function (event, dom, src_snipped_id) { + tests[$(dom).data('src-snippet-id')]['dropped'] = true; + clearTimeout(droptesttime); + droptesttime = setTimeout(droptest,0); + }); +var $thumbnails = $('#oe_snippets div.oe_snippet[data-snippet-id] .oe_snippet_thumbnail'); +$thumbnails.each(function () { + var $thumbnail = $(this); + tests[$thumbnail.parent().data('snippet-id')] = {}; + var position = $thumbnail.position(); + $thumbnail.trigger( $.Event( "mousedown", { which: 1, pageX: position.left, pageY: position.top } ) ); + $thumbnail.trigger( $.Event( "mousemove", { which: 1, pageX: position.left+100, pageY: position.top+100 } ) ); + $first_drop = $(".oe_drop_zone").first(); + position = $first_drop.position(); + $first_drop.trigger( $.Event( "mouseup", { which: 1, pageX: position.left+20, pageY: position.top+20 } ) ); + clearTimeout(droptesttime); + droptesttime = setTimeout(droptest,0); +}); From 53a07c814a667f87902d4821b4fcb6fc5024932d Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Fri, 6 Dec 2013 18:04:14 +0100 Subject: [PATCH 13/14] [FIX] website_hr_recruitment bzr revid: chm@openerp.com-20131206170414-i5yq47pjy79jt1sd --- addons/website_hr_recruitment/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index c287f971ed8..f1d5fc81916 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -36,7 +36,7 @@ class website_hr_recruitment(http.Controller): for job in jobs: if (department and job.department_id.id == department.id) or \ (office and job.address_id.id == office.id): - _jobs.add(job) + _jobs.append(job) jobs = _jobs return request.website.render("website_hr_recruitment.index", { From 20bbabc82b8a54d14557bc307aba6bc49ff60687 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Fri, 6 Dec 2013 23:18:09 +0100 Subject: [PATCH 14/14] [IMP] Huge speed improvement in price computation on products: 162 to 39 queries to render /shop with 28 products. O(n)->O(1) bzr revid: fp@tinyerp.com-20131206221809-x1muf0wonl4ated0 --- addons/product/__openerp__.py | 5 +- addons/product/pricelist.py | 258 ++++++++++------------ addons/product/product.py | 21 +- addons/product/product_data.xml | 1 + addons/product/test/product_pricelist.yml | 16 +- 5 files changed, 138 insertions(+), 163 deletions(-) diff --git a/addons/product/__openerp__.py b/addons/product/__openerp__.py index da17f7d8f6a..194cc91b019 100644 --- a/addons/product/__openerp__.py +++ b/addons/product/__openerp__.py @@ -26,7 +26,10 @@ 'author': 'OpenERP SA', 'category': 'Sales Management', 'depends': ['base', 'process', 'decimal_precision', 'mail'], - 'demo': ['product_demo.xml', 'product_image_demo.xml'], + 'demo': [ + 'product_demo.xml', + 'product_image_demo.xml', + ], 'description': """ This is the base module for managing products and pricelists in OpenERP. ======================================================================== diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py index 07bc38339bd..2f53d67760c 100644 --- a/addons/product/pricelist.py +++ b/addons/product/pricelist.py @@ -146,7 +146,6 @@ class product_pricelist(osv.osv): "currency_id": _get_currency } - #def price_get_multi(self, cr, uid, product_ids, context=None): def price_get_multi(self, cr, uid, pricelist_ids, products_by_qty_by_partner, context=None): """multi products 'price_get'. @param pricelist_ids: @@ -156,23 +155,21 @@ class product_pricelist(osv.osv): 'date': Date of the pricelist (%Y-%m-%d),} @return: a dict of dict with product_id as key and a dict 'price by pricelist' as value """ + if not pricelist_ids: + pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context) + results = {} + for pricelist in self.browse(cr, uid, pricelist_ids, context=context): + subres = self._price_get_multi(cr, uid, pricelist, products_by_qty_by_partner, context=context) + for product_id,price in subres.items(): + results.setdefault(product_id, {}) + results[product_id][pricelist.id] = price + return results - def _create_parent_category_list(id, lst): - if not id: - return [] - parent = product_category_tree.get(id) - if parent: - lst.append(parent) - return _create_parent_category_list(parent, lst) - else: - return lst - # _create_parent_category_list - - if context is None: - context = {} - + def _price_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None): + context = context or {} date = context.get('date') or time.strftime('%Y-%m-%d') + products = map(lambda x: x[0], products_by_qty_by_partner) currency_obj = self.pool.get('res.currency') product_obj = self.pool.get('product.product') product_category_obj = self.pool.get('product.category') @@ -180,154 +177,125 @@ class product_pricelist(osv.osv): supplierinfo_obj = self.pool.get('product.supplierinfo') price_type_obj = self.pool.get('product.price.type') - # product.pricelist.version: - if not pricelist_ids: - pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context) + version = False + for v in pricelist.version_id: + if ((v.date_start is False) or (v.date_start <= date)) and ((v.date_end is False) or (v.date_end >= date)): + version = v + break - pricelist_version_ids = self.pool.get('product.pricelist.version').search(cr, uid, [ - ('pricelist_id', 'in', pricelist_ids), - '|', - ('date_start', '=', False), - ('date_start', '<=', date), - '|', - ('date_end', '=', False), - ('date_end', '>=', date), - ]) - if len(pricelist_ids) != len(pricelist_version_ids): - raise osv.except_osv(_('Warning!'), _("At least one pricelist has no active version !\nPlease create or activate one.")) + categ_ids = {} + for p in products: + categ = p.categ_id + while categ: + categ_ids[categ.id] = True + categ = categ.parent_id + categ_ids = categ_ids.keys() - # product.product: - product_ids = [i[0] for i in products_by_qty_by_partner] - #products = dict([(item['id'], item) for item in product_obj.read(cr, uid, product_ids, ['categ_id', 'product_tmpl_id', 'uos_id', 'uom_id'])]) - products = product_obj.browse(cr, uid, product_ids, context=context) - products_dict = dict([(item.id, item) for item in products]) + prod_ids = [x.id for x in products] + prod_tmpl_ids = [x.product_tmpl_id.id for x in products] - # product.category: - product_category_ids = product_category_obj.search(cr, uid, []) - product_categories = product_category_obj.read(cr, uid, product_category_ids, ['parent_id']) - product_category_tree = dict([(item['id'], item['parent_id'][0]) for item in product_categories if item['parent_id']]) + # Load all rules + cr.execute( + 'SELECT i.id ' + 'FROM product_pricelist_item AS i ' + 'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = any(%s)) ' + 'AND (product_id IS NULL OR (product_id = any(%s))) ' + 'AND ((categ_id IS NULL) OR (categ_id = any(%s))) ' + 'AND (price_version_id = %s) ' + 'ORDER BY sequence, min_quantity desc', + (prod_tmpl_ids, prod_ids, categ_ids, version.id)) + + item_ids = [x[0] for x in cr.fetchall()] + items = self.pool.get('product.pricelist.item').browse(cr, uid, item_ids, context=context) + + price_types = {} results = {} - for product_id, qty, partner in products_by_qty_by_partner: - for pricelist_id in pricelist_ids: - price = False - - tmpl_id = products_dict[product_id].product_tmpl_id and products_dict[product_id].product_tmpl_id.id or False - - categ_id = products_dict[product_id].categ_id and products_dict[product_id].categ_id.id or False - categ_ids = _create_parent_category_list(categ_id, [categ_id]) - if categ_ids: - categ_where = '(categ_id IN (' + ','.join(map(str, categ_ids)) + '))' - else: - categ_where = '(categ_id IS NULL)' - - if partner: - partner_where = 'base <> -2 OR %s IN (SELECT name FROM product_supplierinfo WHERE product_id = %s) ' - partner_args = (partner, tmpl_id) - else: - partner_where = 'base <> -2 ' - partner_args = () - - cr.execute( - 'SELECT i.*, pl.currency_id ' - 'FROM product_pricelist_item AS i, ' - 'product_pricelist_version AS v, product_pricelist AS pl ' - 'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = %s) ' - 'AND (product_id IS NULL OR product_id = %s) ' - 'AND (' + categ_where + ' OR (categ_id IS NULL)) ' - 'AND (' + partner_where + ') ' - 'AND price_version_id = %s ' - 'AND (min_quantity IS NULL OR min_quantity <= %s) ' - 'AND i.price_version_id = v.id AND v.pricelist_id = pl.id ' - 'ORDER BY sequence', - (tmpl_id, product_id) + partner_args + (pricelist_version_ids[0], qty)) - res1 = cr.dictfetchall() - uom_price_already_computed = False - for res in res1: - if res: - if res['base'] == -1: - if not res['base_pricelist_id']: - price = 0.0 - else: - price_tmp = self.price_get(cr, uid, - [res['base_pricelist_id']], product_id, - qty, context=context)[res['base_pricelist_id']] - ptype_src = self.browse(cr, uid, res['base_pricelist_id']).currency_id.id - uom_price_already_computed = True - price = currency_obj.compute(cr, uid, - ptype_src, res['currency_id'], - price_tmp, round=False, - context=context) - elif res['base'] == -2: - # this section could be improved by moving the queries outside the loop: - where = [] - if partner: - where = [('name', '=', partner) ] - sinfo = supplierinfo_obj.search(cr, uid, - [('product_id', '=', tmpl_id)] + where) - price = 0.0 - if sinfo: - qty_in_product_uom = qty - product_default_uom = product_obj.read(cr, uid, [product_id], ['uom_id'])[0]['uom_id'][0] - supplier = supplierinfo_obj.browse(cr, uid, sinfo, context=context)[0] - seller_uom = supplier.product_uom and supplier.product_uom.id or False - if seller_uom and product_default_uom and product_default_uom != seller_uom: - uom_price_already_computed = True - qty_in_product_uom = product_uom_obj._compute_qty(cr, uid, product_default_uom, qty, to_uom_id=seller_uom) - cr.execute('SELECT * ' \ - 'FROM pricelist_partnerinfo ' \ - 'WHERE suppinfo_id IN %s' \ - 'AND min_quantity <= %s ' \ - 'ORDER BY min_quantity DESC LIMIT 1', (tuple(sinfo),qty_in_product_uom,)) - res2 = cr.dictfetchone() - if res2: - price = res2['price'] - else: - price_type = price_type_obj.browse(cr, uid, int(res['base'])) - uom_price_already_computed = True - price = currency_obj.compute(cr, uid, - price_type.currency_id.id, res['currency_id'], - product_obj.price_get(cr, uid, [product_id], - price_type.field, context=context)[product_id], round=False, context=context) - - if price is not False: - price_limit = price - price = price * (1.0+(res['price_discount'] or 0.0)) - price = rounding(price, res['price_round']) #TOFIX: rounding with tools.float_rouding - price += (res['price_surcharge'] or 0.0) - if res['price_min_margin']: - price = max(price, price_limit+res['price_min_margin']) - if res['price_max_margin']: - price = min(price, price_limit+res['price_max_margin']) + for product, qty, partner in products_by_qty_by_partner: + uom_price_already_computed = False + results[product.id] = 0.0 + price = False + for rule in items: + if rule.min_quantity and qtyrule.product_tmpl_id.id: + continue + if rule.product_id and product.id<>rule.product_id.id: + continue + if rule.categ_id: + cat = product.categ_id + while cat: + if cat.id == rule.categ_id.id: break + cat = cat.parent_id + if not cat: + continue - else: - # False means no valid line found ! But we may not raise an - # exception here because it breaks the search - price = False + if rule.base == -1: + if rule.base_pricelist_id: + price_tmp = self._price_get_multi(cr, uid, + rule.base_pricelist_id, [(product, + qty, False)], context=context)[product.id] + ptype_src = rule.base_pricelist_id.currency_id.id + uom_price_already_computed = True + price = currency_obj.compute(cr, uid, + ptype_src, pricelist.currency_id.id, + price_tmp, round=False, + context=context) + elif rule.base == -2: + for seller in product.seller_ids: + if (not partner) or (seller.name.id<>partner): + continue + product_default_uom = product.uom_id.id + seller_uom = seller.product_uom and seller.product_uom.id or False + if seller_uom and product_default_uom and product_default_uom != seller_uom: + uom_price_already_computed = True + qty = product_uom_obj._compute_qty(cr, uid, product_default_uom, qty, to_uom_id=seller_uom) + for line in seller.pricelist_ids: + if line.min_quantity <= qty: + price = line.price - if price: - results['item_id'] = res['id'] - if 'uom' in context and not uom_price_already_computed: - product = products_dict[product_id] - uom = product.uos_id or product.uom_id - price = product_uom_obj._compute_price(cr, uid, uom.id, price, context['uom']) - - if results.get(product_id): - results[product_id][pricelist_id] = price else: - results[product_id] = {pricelist_id: price} + if rule.base not in price_types: + price_types[rule.base] = price_type_obj.browse(cr, uid, int(rule.base)) + price_type = price_types[rule.base] + uom_price_already_computed = True + price = currency_obj.compute(cr, uid, + price_type.currency_id.id, pricelist.currency_id.id, + product_obj._price_get(cr, uid, [product], + price_type.field, context=context)[product.id], round=False, context=context) + + if price is not False: + price_limit = price + price = price * (1.0+(rule.price_discount or 0.0)) + price = rounding(price, rule.price_round) #TOFIX: rounding with tools.float_rouding + price += (rule.price_surcharge or 0.0) + if rule.price_min_margin: + price = max(price, price_limit+rule.price_min_margin) + if rule.price_max_margin: + price = min(price, price_limit+rule.price_max_margin) + break + + if price: + # FP Note: what's this? + # results['item_id'] = res.id + if 'uom' in context and not uom_price_already_computed: + product = products_dict[product.id] + uom = product.uos_id or product.uom_id + price = product_uom_obj._compute_price(cr, uid, uom.id, price, context['uom']) + + results[product.id] = price return results def price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None): - res_multi = self.price_get_multi(cr, uid, pricelist_ids=ids, products_by_qty_by_partner=[(prod_id, qty, partner)], context=context) + product = self.pool.get('product.product').browse(cr, uid, prod_id, context=context) + res_multi = self.price_get_multi(cr, uid, pricelist_ids=ids, products_by_qty_by_partner=[(product, qty, partner)], context=context) res = res_multi[prod_id] res.update({'item_id': {ids[-1]: res_multi.get('item_id', ids[-1])}}) return res - class product_pricelist_version(osv.osv): _name = "product.pricelist.version" _description = "Pricelist Version" diff --git a/addons/product/product.py b/addons/product/product.py index e6ea84b9c85..2a1b20860fc 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -517,6 +517,7 @@ class product_product(osv.osv): return res def _product_price(self, cr, uid, ids, name, arg, context=None): + plobj = self.pool.get('product.pricelist') res = {} if context is None: context = {} @@ -526,18 +527,16 @@ class product_product(osv.osv): if pricelist: # Support context pricelists specified as display_name or ID for compatibility if isinstance(pricelist, basestring): - pricelist_ids = self.pool.get('product.pricelist').name_search( + pricelist_ids = plobj.name_search( cr, uid, pricelist, operator='=', context=context, limit=1) pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist - qtys = map(lambda x: (x, quantity, partner), ids) - price = self.pool.get('product.pricelist').price_get_multi(cr,uid, [pricelist], - qtys, context=context) + products = self.browse(cr, uid, ids, context=context) + qtys = map(lambda x: (x, quantity, partner), products) + pl = plobj.browse(cr, uid, pricelist, context=context) + price = plobj._price_get_multi(cr,uid, pl, qtys, context=context) for id in ids: - try: - res[id] = price[id][pricelist] - except: - price = 0.0 + res[id] = price.get(id, 0.0) for id in ids: res.setdefault(id, 0.0) return res @@ -790,6 +789,10 @@ class product_product(osv.osv): # Could be overrided for variants matrices prices # def price_get(self, cr, uid, ids, ptype='list_price', context=None): + products = self.browse(cr, uid, ids, context=context) + return self._price_get(cr, uid, products, ptype=ptype, context=context) + + def _price_get(self, cr, uid, products, ptype='list_price', context=None): if context is None: context = {} @@ -800,7 +803,7 @@ class product_product(osv.osv): res = {} product_uom_obj = self.pool.get('product.uom') - for product in self.browse(cr, uid, ids, context=context): + for product in products: res[product.id] = product[ptype] or 0.0 if ptype == 'list_price': res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \ diff --git a/addons/product/product_data.xml b/addons/product/product_data.xml index 17109fb7029..517f46a94c1 100644 --- a/addons/product/product_data.xml +++ b/addons/product/product_data.xml @@ -149,6 +149,7 @@ parameter) will see those record just disappear. + 1000 Default Public Pricelist Line diff --git a/addons/product/test/product_pricelist.yml b/addons/product/test/product_pricelist.yml index 0b4bed18a82..60a4a4bf66f 100644 --- a/addons/product/test/product_pricelist.yml +++ b/addons/product/test/product_pricelist.yml @@ -6,19 +6,19 @@ !python {model: product.product}: | context.update({'pricelist': ref("customer_pricelist"), 'quantity':1}) product = self.browse(cr, uid, ref("product_product_4"), context=context) - assert product.price == (product.lst_price-product.lst_price*(0.10)), "Sell price is not correspond." + assert product.price == (product.lst_price-product.lst_price*(0.10)), "Wrong sale price." - I check sale price of Laptop. - !python {model: product.product}: | product = self.browse(cr, uid, ref("product_product_25"), context=context) - assert product.price == product.lst_price + 1, "Sell price is not correspond." + assert product.price == product.lst_price + 1, "Wrong sale price." - I check sale price of IT component. - !python {model: product.product}: | product = self.browse(cr, uid, ref("product_product_7"), context=context) - assert product.price == product.lst_price, "Sell price is not correspond." + assert product.price == product.lst_price, "Wrong sale price." - I check sale price of IT component if more than 3 Unit. @@ -26,14 +26,14 @@ !python {model: product.product}: | context.update({'quantity':5}) product = self.browse(cr, uid, ref("product_product_26"), context=context) - assert product.price == product.lst_price-product.lst_price*(0.05), "Sell price is not correspond." + assert product.price == product.lst_price-product.lst_price*(0.05), "Wrong sale price." - I check sale price of LCD Monitor. - !python {model: product.product}: | context.update({'quantity':1}) product = self.browse(cr, uid, ref("product_product_6"), context=context) - assert product.price == product.lst_price, "Sell price is not correspond." + assert product.price == product.lst_price, "Wrong sale price." - I check sale price of LCD Monitor on end of year. @@ -41,7 +41,7 @@ !python {model: product.product}: | context.update({'quantity':1, 'date': '2011-12-31'}) product = self.browse(cr, uid, ref("product_product_6"), context=context) - assert product.price == product.lst_price-product.lst_price*(0.30), "Sell price is not correspond." + assert product.price == product.lst_price-product.lst_price*(0.30), "Wrong sale price." - I check cost price of LCD Monitor. @@ -49,14 +49,14 @@ !python {model: product.product}: | context.update({'quantity':1, 'date': False, 'partner': ref('base.res_partner_4'), 'pricelist': ref("supplier_pricelist")}) product = self.browse(cr, uid, ref("product_product_6"), context=context) - assert product.price == 792, "cost price is not correspond." + assert product.price == 792, "wrong cost price." - I check cost price of LCD Monitor if more than 3 Unit. - !python {model: product.product}: | context.update({'quantity':3}) product = self.browse(cr, uid, ref("product_product_6"), context=context) - assert product.price == 787, "cost price is not correspond." + assert product.price == 787, "wrong cost price." - I print the sale prices report.