From d9eb14bb9b3f29471a25261beca63ee03367058e Mon Sep 17 00:00:00 2001 From: Daniel Gergely Date: Tue, 14 Apr 2026 17:48:57 +0200 Subject: [PATCH 1/3] [T3090] WIP: extract utm variables from url --- my_compassion/controllers/my2_sponsorships.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/my_compassion/controllers/my2_sponsorships.py b/my_compassion/controllers/my2_sponsorships.py index 61cf6a2c..af7f6237 100644 --- a/my_compassion/controllers/my2_sponsorships.py +++ b/my_compassion/controllers/my2_sponsorships.py @@ -200,6 +200,73 @@ def _get_filtered_domain(cls, post): class MyCompassionNewSponsorshipController(http.Controller): + + @staticmethod + def _extract_utm_information() -> dict: + """ + Extracts utm medium, source and campaign information + from the session and returns it. + Return: + a dictionary containing utm information or empty dict + """ + utm_medium_str = request.session.get("wizard_utm_medium") + utm_source_str = request.session.get("wizard_utm_source") + utm_campaign_str = request.session.get("wizard_utm_campaign") + + if utm_medium_str or utm_source_str or utm_campaign_str: + utm_vals = {} + + if utm_medium_str: + medium = ( + request.env["utm.medium"] + .sudo() + .search([("name", "=ilike", utm_medium_str)], limit=1) + ) + if not medium: + medium = ( + request.env["utm.medium"] + .sudo() + .create({"name": utm_medium_str}) + ) + utm_vals["medium_id"] = medium.id + + if utm_source_str: + source = ( + request.env["utm.source"] + .sudo() + .search([("name", "=ilike", utm_source_str)], limit=1) + ) + if not source: + source = ( + request.env["utm.source"] + .sudo() + .create({"name": utm_source_str}) + ) + utm_vals["source_id"] = source.id + + if utm_campaign_str: + campaign = ( + request.env["utm.campaign"] + .sudo() + .search([("name", "=ilike", utm_campaign_str)], limit=1) + ) + if not campaign: + campaign = ( + request.env["utm.campaign"] + .sudo() + .create({"name": utm_campaign_str}) + ) + utm_vals["campaign_id"] = campaign.id + + # Clean up the session variables so they don't bleed into future organic sessions + request.session.pop("wizard_utm_medium", None) + request.session.pop("wizard_utm_source", None) + request.session.pop("wizard_utm_campaign", None) + + return utm_vals + return {} + + @http.route( '/my2/new-sponsorship/', type="http", @@ -212,6 +279,17 @@ def wizard_start(self, child, sponsorship_type="standard", **kwargs): return: An HTTP response containing a rendered template with the initial wizard page. """ + # capture and store utm information + utm_medium = kwargs.get("utm_medium") + utm_source = kwargs.get("utm_source") + utm_campaign = kwargs.get("utm_campaign") + + if utm_medium: + request.session["wizard_utm_medium"] = utm_medium + if utm_source: + request.session["wizard_utm_source"] = utm_source + if utm_campaign: + request.session["wizard_utm_campaign"] = utm_campaign # Make sure child is available and reserve it for 5 minutes if child.state not in child._available_states(): raise NotFound() @@ -294,6 +372,10 @@ def sponsorship_wizard_submit(self, **post): raise Gone() sponsorship = wizard.finish_sponsorship() + utm_values = self._extract_utm_information() + if utm_values: + sponsorship.sudo().write(utm_values) + # Redirect to thank-you page return request.redirect( f"/my2/new-sponsorship/thank-you?sponsorship_id={sponsorship.id}" From fbc6d18940f6b0efd777994462253abbb4b7e605 Mon Sep 17 00:00:00 2001 From: Daniel Gergely Date: Wed, 15 Apr 2026 16:23:15 +0200 Subject: [PATCH 2/3] [T3090] REFACTOR: using id instead of child object - FIX: this enables unauthenticated users to access a child's page (when for example scanning a qr code on a child pack) --- my_compassion/controllers/my2_sponsorships.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/my_compassion/controllers/my2_sponsorships.py b/my_compassion/controllers/my2_sponsorships.py index af7f6237..98c20ba4 100644 --- a/my_compassion/controllers/my2_sponsorships.py +++ b/my_compassion/controllers/my2_sponsorships.py @@ -200,7 +200,6 @@ def _get_filtered_domain(cls, post): class MyCompassionNewSponsorshipController(http.Controller): - @staticmethod def _extract_utm_information() -> dict: """ @@ -258,7 +257,8 @@ def _extract_utm_information() -> dict: ) utm_vals["campaign_id"] = campaign.id - # Clean up the session variables so they don't bleed into future organic sessions + # Clean up the session variables + # so they don't bleed into future organic sessions request.session.pop("wizard_utm_medium", None) request.session.pop("wizard_utm_source", None) request.session.pop("wizard_utm_campaign", None) @@ -266,19 +266,27 @@ def _extract_utm_information() -> dict: return utm_vals return {} - @http.route( - '/my2/new-sponsorship/', + "/my2/new-sponsorship/", type="http", auth="public", website=True, ) - def wizard_start(self, child, sponsorship_type="standard", **kwargs): + def wizard_start(self, child_id, sponsorship_type="standard", **kwargs): """ Renders the new sponsorship wizard initial page. return: An HTTP response containing a rendered template with the initial wizard page. """ + child = ( + request.env["compassion.child"] + .sudo() + .search([("id", "=", child_id)], limit=1) + ) + + if not child: + raise NotFound("Child not found in database") + # capture and store utm information utm_medium = kwargs.get("utm_medium") utm_source = kwargs.get("utm_source") From 7556502287e3426023bae0aa3d8926a5b1654457 Mon Sep 17 00:00:00 2001 From: Daniel Gergely Date: Thu, 16 Apr 2026 10:02:57 +0200 Subject: [PATCH 3/3] [T3090] FIX: GCA suggestions - REFACTOR: using for loop for more efficiency - FIX: campaign is now not auto created. needs to exist before assignment, otherwise it will be ignored. - FIX: using int instead of string for child id, and browse instead of a normal search --- my_compassion/controllers/my2_sponsorships.py | 84 ++++++------------- 1 file changed, 25 insertions(+), 59 deletions(-) diff --git a/my_compassion/controllers/my2_sponsorships.py b/my_compassion/controllers/my2_sponsorships.py index 98c20ba4..253df848 100644 --- a/my_compassion/controllers/my2_sponsorships.py +++ b/my_compassion/controllers/my2_sponsorships.py @@ -207,67 +207,37 @@ def _extract_utm_information() -> dict: from the session and returns it. Return: a dictionary containing utm information or empty dict + IMPORTANT: + the utms need to be created first, otherwise they + will be ignored! """ - utm_medium_str = request.session.get("wizard_utm_medium") - utm_source_str = request.session.get("wizard_utm_source") - utm_campaign_str = request.session.get("wizard_utm_campaign") - - if utm_medium_str or utm_source_str or utm_campaign_str: + utm_mapping = { + "wizard_utm_medium": ("utm.medium", "medium_id"), + "wizard_utm_source": ("utm.source", "source_id"), + "wizard_utm_campaign": ("utm.campaign", "campaign_id"), + } + session_vals = {k: request.session.get(k) for k in utm_mapping} + if any(session_vals.values()): utm_vals = {} - - if utm_medium_str: - medium = ( - request.env["utm.medium"] - .sudo() - .search([("name", "=ilike", utm_medium_str)], limit=1) - ) - if not medium: - medium = ( - request.env["utm.medium"] - .sudo() - .create({"name": utm_medium_str}) - ) - utm_vals["medium_id"] = medium.id - - if utm_source_str: - source = ( - request.env["utm.source"] - .sudo() - .search([("name", "=ilike", utm_source_str)], limit=1) - ) - if not source: - source = ( - request.env["utm.source"] + for k, val in session_vals.items(): + if val: + model, f_id = utm_mapping[k] + rec = ( + request.env[model] .sudo() - .create({"name": utm_source_str}) + .search([("name", "=ilike", val)], limit=1) ) - utm_vals["source_id"] = source.id - - if utm_campaign_str: - campaign = ( - request.env["utm.campaign"] - .sudo() - .search([("name", "=ilike", utm_campaign_str)], limit=1) - ) - if not campaign: - campaign = ( - request.env["utm.campaign"] - .sudo() - .create({"name": utm_campaign_str}) - ) - utm_vals["campaign_id"] = campaign.id - - # Clean up the session variables - # so they don't bleed into future organic sessions - request.session.pop("wizard_utm_medium", None) - request.session.pop("wizard_utm_source", None) - request.session.pop("wizard_utm_campaign", None) - + # only assign if campaign already exists + if rec: + utm_vals[f_id] = rec.id + # clean up the session variables + for k in utm_mapping: + request.session.pop(k, None) return utm_vals return {} @http.route( - "/my2/new-sponsorship/", + "/my2/new-sponsorship/", type="http", auth="public", website=True, @@ -278,13 +248,9 @@ def wizard_start(self, child_id, sponsorship_type="standard", **kwargs): return: An HTTP response containing a rendered template with the initial wizard page. """ - child = ( - request.env["compassion.child"] - .sudo() - .search([("id", "=", child_id)], limit=1) - ) + child = request.env["compassion.child"].sudo().browse(child_id) - if not child: + if not child.exists(): raise NotFound("Child not found in database") # capture and store utm information