# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). 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 # res.partner; we need them as accepted qcontext params so the signup form # 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") 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. # # We ALSO keep a composed `name` (firstname + " " + lastname) in the # values, on purpose. The core's `_create_user_from_template` # (`auth_signup/models/res_users.py`) rejects the signup with # `ValueError: Signup: no name or partner given for new user` if # neither `partner_id` nor `name` is present in the values — that # check runs BEFORE `template_user.copy(values)` reaches # `res.partner.create`, where `partner_firstname` would otherwise # recompute `name` from the split fields. # # By providing the composed `name`, the check passes; then, inside # `res.partner.create`, partner_firstname's override detects that both # `firstname` and `lastname` are in the vals and DISCARDS the composed # `name` (see `partner_firstname/models/res_partner.py:_name_fields_in_vals` # branch `del vals["name"]`), recomputing it from the split fields. # Net effect: the partner ends up with the proper `firstname`, # `lastname` and a recomputed `name`, exactly as intended. firstname = qcontext.get("firstname", "") lastname = qcontext.get("lastname", "") if firstname or lastname: values["firstname"] = firstname values["lastname"] = lastname partner_model = request.env["res.partner"] values["name"] = partner_model._get_computed_name( lastname, firstname ) return values @http.route() def web_auth_signup(self, *args, **kw): response = super().web_auth_signup(*args, **kw) if request.session.uid: # 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 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 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")