From bb81e4ccd7b0044bbe1ad45842a3f799ceac0838 Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Wed, 26 Jun 2019 09:25:47 +0000 Subject: [PATCH 1/3] [FIX] website_sale: avoid adding website on customer when not needed Before this commit, when an address would be edited or created from checkout, we would always add a website_id on it. (values_postprocess -> _checkout_form_save) That would be incorrect if the website has not 'specific user account' enabled. It would lead to an error as the user would not be able to connect to another website. Indeed, the billing address is stored on the partner directly. Since the res.user website_id is a related to the partner one, the user would then be restricted to that website. Closes #34396 Fixes #34308 --- addons/website_sale/controllers/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 88a8bb27fa5..8b37915e6be 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -605,7 +605,9 @@ class WebsiteSale(ProductConfiguratorController): new_values['customer'] = True new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id new_values['user_id'] = request.website.salesperson_id and request.website.salesperson_id.id - new_values['website_id'] = request.website.id + + if request.website.specific_user_account: + new_values['website_id'] = request.website.id if order.partner_id.id == request.website.user_id.sudo().partner_id.id: # If the partner is public, we assign the company of the website From 6ebcb1db80eeb37ab7ac2f5a27a8c616f12b95b3 Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Wed, 26 Jun 2019 13:48:56 +0000 Subject: [PATCH 2/3] [IMP] tool, website, website_sale: move test utils functions We have multiple ways of simulating a frontend context while running python tests (mainly during TransactionCase): - `MockObject` class from 8557bcfa30 - `simulate_frontend_context` method from a5a5a57e48 - `DotDict` from f8efc9a957c28 This commit moves `MockObject` to misc to be reusable and adapt it to be accessible by dot notation with `DotDict`. Part of #34396 --- addons/website/tests/test_qweb.py | 53 +++------------- addons/website/tools.py | 60 +++++++++++++++++++ .../tests/test_website_sale_pricelist.py | 8 +-- odoo/tools/misc.py | 11 ++++ 4 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 addons/website/tools.py diff --git a/addons/website/tests/test_qweb.py b/addons/website/tests/test_qweb.py index 8cba2d150c0..b752d641f40 100644 --- a/addons/website/tests/test_qweb.py +++ b/addons/website/tests/test_qweb.py @@ -2,10 +2,9 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import re -import werkzeug -import odoo from odoo import tools +from odoo.addons.website.tools import MockRequest from odoo.modules.module import get_module_resource from odoo.tests.common import TransactionCase @@ -66,42 +65,6 @@ class TestQweb(TransactionCase): "user_id": demo.id, }).encode('utf8')) -class MockObject(object): - _log_call = [] - def __init__(self, *args, **kwargs): - self.__dict__ = kwargs - def __call__(self, *args, **kwargs): - self._log_call.append((args, kwargs)) - return self - def __getitem__(self, index): - return self - -def werkzeugRaiseNotFound(*args, **kwargs): - raise werkzeug.exceptions.NotFound() - -class MockRequest(object): - """ Class with context manager mocking odoo.http.request for tests """ - def __init__(self, env, website=None, context=None, multilang=True, routing=True): - app = MockObject(routing={ - 'type': 'http', - 'website': True, - 'multilang': multilang, - }) - app.get_db_router = app.bind = app.match = app - if not routing: - app.match = werkzeugRaiseNotFound - self.request = MockObject( - env=env, context=context or {}, db=None, debug=False, - website=website, httprequest=MockObject( - path='/hello/', - app=app - ) - ) - odoo.http._request_stack.push(self.request) - def __enter__(self): - return self.request - def __exit__(self, exc_type, exc_value, traceback): - odoo.http._request_stack.pop() class TestQwebProcessAtt(TransactionCase): def setUp(self): @@ -128,7 +91,7 @@ class TestQwebProcessAtt(TransactionCase): self._test_att('/a', {'href': '/a'}) def test_process_att_no_website(self): - with MockRequest(self.env) as request: + with MockRequest(self.env): # no website so URL rewriting self._test_att('/', {'href': '/'}) self._test_att('/en_US/', {'href': '/en_US/'}) @@ -137,7 +100,7 @@ class TestQwebProcessAtt(TransactionCase): self._test_att('/a', {'href': '/a'}) def test_process_att_monolang_route(self): - with MockRequest(self.env, website=self.website, multilang=False) as request: + with MockRequest(self.env, website=self.website, multilang=False): # lang not changed in URL but CDN enabled self._test_att('/a', {'href': 'http://test.cdn/a'}) self._test_att('/en_US/a', {'href': 'http://test.cdn/en_US/a'}) @@ -145,19 +108,19 @@ class TestQwebProcessAtt(TransactionCase): self._test_att('/en_US/b', {'href': '/en_US/b'}) def test_process_att_no_request_lang(self): - with MockRequest(self.env, self.website) as request: + with MockRequest(self.env, website=self.website): self._test_att('/', {'href': '/'}) self._test_att('/en_US/', {'href': '/'}) self._test_att('/fr_FR/', {'href': '/fr_FR/'}) def test_process_att_with_request_lang(self): - with MockRequest(self.env, self.website, context={'lang': 'fr_FR'}) as request: + with MockRequest(self.env, website=self.website, context={'lang': 'fr_FR'}): self._test_att('/', {'href': '/fr_FR/'}) self._test_att('/en_US/', {'href': '/'}) self._test_att('/fr_FR/', {'href': '/fr_FR/'}) def test_process_att_matching_cdn_and_lang(self): - with MockRequest(self.env, self.website) as request: + with MockRequest(self.env, website=self.website): # lang prefix is added before CDN self._test_att('/a', {'href': 'http://test.cdn/a'}) self._test_att('/en_US/a', {'href': 'http://test.cdn/a'}) @@ -167,13 +130,13 @@ class TestQwebProcessAtt(TransactionCase): self._test_att('/fr_FR/b', {'href': '/fr_FR/b'}) def test_process_att_no_route(self): - with MockRequest(self.env, self.website, context={'lang': 'fr_FR'}, routing=False) as request: + with MockRequest(self.env, website=self.website, context={'lang': 'fr_FR'}, routing=False): # default on multilang=True if route is not /{module}/static/ self._test_att('/web/static/hi', {'href': '/web/static/hi'}) self._test_att('/my-page', {'href': '/fr_FR/my-page'}) def test_process_att_url_crap(self): - with MockRequest(self.env, self.website) as request: + with MockRequest(self.env, website=self.website) as request: # #{fragment} is stripped from URL when testing route self._test_att('/x#y?z', {'href': '/x#y?z'}) self.assertEqual( diff --git a/addons/website/tools.py b/addons/website/tools.py new file mode 100644 index 00000000000..fde1e94651d --- /dev/null +++ b/addons/website/tools.py @@ -0,0 +1,60 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo +import werkzeug + +from odoo.tools import DotDict + + +class MockObject(object): + _log_call = [] + + def __init__(self, *args, **kwargs): + self.__dict__ = kwargs + + def __call__(self, *args, **kwargs): + self._log_call.append((args, kwargs)) + return self + + def __getitem__(self, index): + return self + + +def werkzeugRaiseNotFound(*args, **kwargs): + raise werkzeug.exceptions.NotFound() + + +class MockRequest(object): + """ Class with context manager mocking odoo.http.request for tests """ + def __init__(self, env, **kw): + app = MockObject(routing={ + 'type': 'http', + 'website': True, + 'multilang': kw.get('multilang', True), + }) + app.get_db_router = app.bind = app.match = app + if not kw.get('routing', True): + app.match = werkzeugRaiseNotFound + self.request = DotDict({ + 'context': kw.get('context', {}), + 'db': None, + 'debug': False, + 'env': env, + 'httprequest': { + 'path': '/hello/', + 'app': app, + }, + 'redirect': werkzeug.utils.redirect, + 'session': { + 'sale_order_id': kw.get('sale_order_id'), + }, + 'website': kw.get('website'), + }) + odoo.http._request_stack.push(self.request) + + def __enter__(self): + return self.request + + def __exit__(self, exc_type, exc_value, traceback): + odoo.http._request_stack.pop() diff --git a/addons/website_sale/tests/test_website_sale_pricelist.py b/addons/website_sale/tests/test_website_sale_pricelist.py index eb3b9d5d1f6..9190ebc7dc5 100644 --- a/addons/website_sale/tests/test_website_sale_pricelist.py +++ b/addons/website_sale/tests/test_website_sale_pricelist.py @@ -5,6 +5,7 @@ try: except ImportError: from mock import patch from odoo.tests.common import HttpCase, TransactionCase +from odoo.tools import DotDict ''' /!\/!\ Calling `get_pricelist_available` after setting `property_product_pricelist` on @@ -154,13 +155,6 @@ def simulate_frontend_context(self, website_id=1): self.addCleanup(patcher.stop) -class DotDict(dict): - """dot.notation access to dictionary attributes""" - def __getattr__(*args): - val = dict.get(*args) - return DotDict(val) if type(val) is dict else val - - class TestWebsitePriceListAvailable(TransactionCase): # This is enough to avoid a mock (request.session/website do not exist during test) def get_pricelist_available(self, show_visible=False, website_id=1, country_code=None, website_sale_current_pl=None): diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 762c07e535c..7d465356e10 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -1242,3 +1242,14 @@ def wrap_module(module, attr_list): raise AttributeError(attrib) # module and attr_list are in the closure return WrappedModule() + + +class DotDict(dict): + """Helper for dot.notation access to dictionary attributes + E.g. + foo = DotDict({'bar': False}) + return foo.bar + """ + def __getattr__(self, attrib): + val = self.get(attrib) + return DotDict(val) if type(val) is dict else val From 9d5131a696c6e168fe4a802442c868b5dd6eb4ce Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Wed, 26 Jun 2019 14:00:07 +0000 Subject: [PATCH 3/3] [FIX] website_sale: add tests Parent commit moved needed functions into misc so this test could use those. This test is for parent's parent commit which fix checkout billing address edition. Part of #34396 --- .../website_sale/tests/test_sale_process.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/addons/website_sale/tests/test_sale_process.py b/addons/website_sale/tests/test_sale_process.py index ae3f63e436f..6609b4e4d9c 100644 --- a/addons/website_sale/tests/test_sale_process.py +++ b/addons/website_sale/tests/test_sale_process.py @@ -1,7 +1,11 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + import odoo.tests +from odoo.addons.website_sale.controllers.main import WebsiteSale +from odoo.addons.website.tools import MockRequest + @odoo.tests.tagged('post_install', '-at_install') class TestUi(odoo.tests.HttpCase): @@ -14,4 +18,28 @@ class TestUi(odoo.tests.HttpCase): def test_03_demo_checkout(self): self.phantom_js("/", "odoo.__DEBUG__.services['web_tour.tour'].run('shop_buy_product')", "odoo.__DEBUG__.services['web_tour.tour'].tours.shop_buy_product.ready", login="demo") - # TO DO - add public test with new address when convert to web.tour format. + +@odoo.tests.tagged('post_install', '-at_install') +class TestWebsiteSaleCheckoutAddress(odoo.tests.TransactionCase): + def test_01_edit_billing_address(self): + website = self.env['website'].browse(1) + p = self.env.user.partner_id + so = self.env['sale.order'].create({ + 'partner_id': p.id, + 'website_id': website.id, + 'order_line': [(0, 0, { + 'product_id': self.env['product.product'].create({'name': 'Product A', 'list_price': 100}).id, + 'name': 'Product A', + })] + }) + country_id = self.env['res.country'].search([], limit=1).id + + WebsiteSaleController = WebsiteSale() + with MockRequest(self.env, website=website, sale_order_id=so.id): + WebsiteSaleController.address(name='website_sale test user', email='email@email.email', street='ooo', city='ooo', country_id=country_id, submitted=1, partner_id=p.id) + self.assertFalse(p.website_id, "Partner should not have a website set on him after editing billing.") + + website.specific_user_account = True + with MockRequest(self.env, website=website, sale_order_id=so.id): + WebsiteSaleController.address(name='website_sale test user', email='email@email.email', street='ooo', city='ooo', country_id=country_id, submitted=1, partner_id=p.id) + self.assertEqual(p.website_id, website, "Partner should have a website set on him after editing billing.")