From ba52c72b788b78759c0d20aa165db1fdced4c227 Mon Sep 17 00:00:00 2001 From: luis Date: Wed, 22 Jul 2026 17:42:28 +0200 Subject: [PATCH] website_membership_signup_required: accept terms & conditions in signup form. Redirect to cart --- .../controllers/main.py | 74 ++++++++++++++++--- website_membership_signup_required/i18n/es.po | 16 ++++ .../website_membership_signup_required.pot | 16 ++++ .../website_membership_signup_required.js | 21 +++--- .../tests/test_membership_signup.py | 30 ++++++++ .../views/auth_signup_templates.xml | 16 ++++ 6 files changed, 151 insertions(+), 22 deletions(-) diff --git a/website_membership_signup_required/controllers/main.py b/website_membership_signup_required/controllers/main.py index 86ffd21..38ce0c0 100644 --- a/website_membership_signup_required/controllers/main.py +++ b/website_membership_signup_required/controllers/main.py @@ -1,8 +1,9 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import http +from odoo import _, http from odoo.addons.auth_signup.controllers.main import AuthSignupHome from odoo.addons.web.controllers.home import SIGN_UP_REQUEST_PARAMS +from odoo.exceptions import UserError from odoo.http import request # partner_firstname exposes `firstname` / `lastname` on res.users and @@ -10,6 +11,11 @@ from odoo.http import request # roundtrips them transparently through `get_auth_signup_qcontext`. SIGN_UP_REQUEST_PARAMS.add("firstname") SIGN_UP_REQUEST_PARAMS.add("lastname") +# `accepted_terms` carries the "I agree to the terms & conditions" +# checkbox value from the signup form. Adding it here makes the value +# round-trip through `get_auth_signup_qcontext` so we can validate it +# server-side in `_prepare_signup_values`. +SIGN_UP_REQUEST_PARAMS.add("accepted_terms") # Session keys used by the membership signup flow. MEMBERSHIP_SIGNUP_SESSION_KEYS = ("membership_signup_active", "membership_signup_product_id") @@ -19,6 +25,15 @@ class AuthSignupHomeMembership(AuthSignupHome): def _prepare_signup_values(self, qcontext): values = super()._prepare_signup_values(qcontext) + # Legal terms acceptance: the signup form includes a mandatory + # checkbox "I agree to the terms & conditions". The POST value is + # 'accepted' when checked. Reject the submission with a UserError + # (the core web_auth_signup controller catches UserError and exposes + # it as `qcontext['error']` which the template renders as a red + # alert above the form, so no extra plumbing is needed). The browser + # also enforces it client-side via `required="required"`. + if qcontext.get("accepted_terms") != "accepted": + raise UserError(_("You must accept the terms and conditions.")) # partner_firstname: the signup form collects `firstname` / `lastname` # separately. We inject both into the signup values so the resulting # partner/user has them populated. @@ -53,22 +68,59 @@ class AuthSignupHomeMembership(AuthSignupHome): @http.route() def web_auth_signup(self, *args, **kw): response = super().web_auth_signup(*args, **kw) - # On a successful signup the user is now authenticated (session.uid - # set) and the redirect to the product page will happen. Drop the - # membership markers from the session so they cannot leak into later - # /web/signup attempts by other visitors sharing the session (defence - # in depth). if request.session.uid: - for key in MEMBERSHIP_SIGNUP_SESSION_KEYS: - request.session.pop(key, None) + # Successful signup: if this is a membership flow, add the + # pending membership product to the now-authenticated cart and + # redirect straight to /shop/cart (instead of going back to the + # product page as the core would, via the `redirect` query + # param). Otherwise fall through to the regular response. + redirect = self._membership_post_login_add_to_cart() + if redirect is not None: + return redirect return response @http.route() def web_login(self, *args, **kw): response = super().web_login(*args, **kw) - # Same cleanup on explicit login (covers the "Already have an - # account?" branch from the signup page). + # Same handling on explicit login: covers the "Already have an + # account?" branch from the signup page (option B agreed with the + # client): both signup and login auto-add the pending membership + # product and go straight to the cart. if request.session.uid: + redirect = self._membership_post_login_add_to_cart() + if redirect is not None: + return redirect + return response + + def _membership_post_login_add_to_cart(self): + """Add the pending membership product (if any, set by the + add-to-cart interception on `/shop/cart/update`) to the + now-authenticated user's cart and redirect to `/shop/cart`. + + Returns a Response (redirect to /shop/cart) when a membership + product was pending in the session, or `None` when none was — + in the latter case the caller should fall through to the + original response (e.g., a normal login that did not originate + from the membership flow). + + Side effect: always clears the membership session markers + (`membership_signup_active`, `membership_signup_product_id`), + whether a pending product was present or not, so they cannot + leak into a later request. + """ + product_id = request.session.get("membership_signup_product_id") + if not product_id: for key in MEMBERSHIP_SIGNUP_SESSION_KEYS: request.session.pop(key, None) - return response \ No newline at end of file + return None + # Get (or create) the SO for the now-authenticated web user and + # add the pending membership product to it. + order = request.website.sale_get_order(force_create=True) + order._cart_update(product_id=int(product_id), add_qty=1) + # Keep the cart navbar badge in sync (the core sets this on its + # own /shop/cart/update_json path; we bypass that here). + request.session["website_sale_cart_quantity"] = order.cart_quantity + # Drop the markers: the membership flow is now complete. + for key in MEMBERSHIP_SIGNUP_SESSION_KEYS: + request.session.pop(key, None) + return request.redirect("/shop/cart") \ No newline at end of file diff --git a/website_membership_signup_required/i18n/es.po b/website_membership_signup_required/i18n/es.po index 2b1e85c..77519b3 100644 --- a/website_membership_signup_required/i18n/es.po +++ b/website_membership_signup_required/i18n/es.po @@ -26,6 +26,22 @@ msgstr "p. ej. García" msgid "e.g. John" msgstr "p. ej. Juan" +#. module: website_membership_signup_required +#: model:ir.ui.view,arch_db:website_membership_signup_required.auth_signup_terms_checkbox +msgid "I agree to the" +msgstr "Acepto los " + +#. module: website_membership_signup_required +#: model:ir.ui.view,arch_db:website_membership_signup_required.auth_signup_terms_checkbox +msgid "terms & conditions" +msgstr "términos y condiciones" + +#. module: website_membership_signup_required +#. odoo-python +#: code:addons/website_membership_signup_required/controllers/main.py:0 +msgid "You must accept the terms and conditions." +msgstr "Debes aceptar los términos y condiciones." + #. module: website_membership_signup_required #: model:ir.ui.view,arch_db:website_membership_signup_required.auth_signup_fields_firstname_lastname msgid "First name" diff --git a/website_membership_signup_required/i18n/website_membership_signup_required.pot b/website_membership_signup_required/i18n/website_membership_signup_required.pot index c599f4e..a7f53e5 100644 --- a/website_membership_signup_required/i18n/website_membership_signup_required.pot +++ b/website_membership_signup_required/i18n/website_membership_signup_required.pot @@ -25,6 +25,22 @@ msgstr "" msgid "e.g. John" msgstr "" +#. module: website_membership_signup_required +#: model:ir.ui.view,arch_db:website_membership_signup_required.auth_signup_terms_checkbox +msgid "I agree to the" +msgstr "" + +#. module: website_membership_signup_required +#: model:ir.ui.view,arch_db:website_membership_signup_required.auth_signup_terms_checkbox +msgid "terms & conditions" +msgstr "" + +#. module: website_membership_signup_required +#. odoo-python +#: code:addons/website_membership_signup_required/controllers/main.py:0 +msgid "You must accept the terms and conditions." +msgstr "" + #. module: website_membership_signup_required #: model:ir.ui.view,arch_db:website_membership_signup_required.auth_signup_fields_firstname_lastname msgid "First name" diff --git a/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js b/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js index 8be5daf..173b806 100644 --- a/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js +++ b/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js @@ -59,23 +59,22 @@ registry.category("web_tour.tours").add("website_membership_signup_required_tour run: "edit TourMember1!", }, { - content: "Submit the signup form -> redirect back to product page", + content: "Accept the terms & conditions checkbox", + trigger: ".oe_signup_form input[name='accepted_terms']", + run: "click", + }, + { + content: "Submit the signup form -> redirect straight to cart " + + "with the product already added (post-login auto-add)", trigger: ".oe_signup_form button[type='submit']", run: "click", expectUnloadPage: true, }, - // -- Back on the product page, now authenticated -------------------- + // -- Lands directly on /shop/cart with the membership line --------- { - content: "We're back on the membership product page (authenticated)", - trigger: `#product_details h1:contains("${PRODUCT_NAME}")`, + content: "We land on /shop/cart with the membership product", + trigger: `#cart_products td.t-w-employee a:text("${PRODUCT_NAME}")`, }, - { - content: "Add to cart as authenticated user", - trigger: "#add_to_cart", - run: "click", - expectUnloadPage: true, - }, - tourUtils.goToCart(), tourUtils.assertCartContains({ productName: PRODUCT_NAME }), ], }); diff --git a/website_membership_signup_required/tests/test_membership_signup.py b/website_membership_signup_required/tests/test_membership_signup.py index bd4e277..6caa1ef 100644 --- a/website_membership_signup_required/tests/test_membership_signup.py +++ b/website_membership_signup_required/tests/test_membership_signup.py @@ -2,6 +2,7 @@ import werkzeug.datastructures +from odoo.exceptions import UserError from odoo.fields import Date from odoo.addons.website_membership_signup_required.controllers.main import ( @@ -117,6 +118,7 @@ class TestMembershipSignup(TransactionCase): "confirm_password": "verystrongpwd", "firstname": "John", "lastname": "Doe", + "accepted_terms": "accepted", } with MockRequest(self.env, website=self.website): values = controller._prepare_signup_values(qcontext) @@ -131,6 +133,34 @@ class TestMembershipSignup(TransactionCase): self.assertIn("John", values["name"]) self.assertIn("Doe", values["name"]) + def test_07b_prepare_signup_values_reject_without_terms(self): + """Signup must be rejected if the legal terms checkbox is not + checked (`accepted_terms` missing or not 'accepted'). + """ + import unittest + controller = AuthSignupHome() + base_qcontext = { + "login": "no.terms@example.com", + "password": "verystrongpwd", + "confirm_password": "verystrongpwd", + "firstname": "No", + "lastname": "Terms", + } + # Missing the key entirely. + with MockRequest(self.env, website=self.website): + with self.assertRaises(UserError): + controller._prepare_signup_values(dict(base_qcontext)) + # Present but not 'accepted'. + qcontext = dict(base_qcontext, accepted_terms="") + with MockRequest(self.env, website=self.website): + with self.assertRaises(UserError): + controller._prepare_signup_values(qcontext) + # Present and 'accepted' -> does not raise. + qcontext = dict(base_qcontext, accepted_terms="accepted") + with MockRequest(self.env, website=self.website): + values = controller._prepare_signup_values(qcontext) + self.assertEqual(values.get("firstname"), "No") + # -- RF-08 ------------------------------------------------------------- def test_08_backend_sale_order_with_membership_product_without_user(self): diff --git a/website_membership_signup_required/views/auth_signup_templates.xml b/website_membership_signup_required/views/auth_signup_templates.xml index 1b8c975..f584d7a 100644 --- a/website_membership_signup_required/views/auth_signup_templates.xml +++ b/website_membership_signup_required/views/auth_signup_templates.xml @@ -21,4 +21,20 @@ + + \ No newline at end of file