Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 31 additions & 25 deletions website_membership_registration/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,34 +71,40 @@ def _compute_membership_group_ids(self):
@api.constrains("email", "membership_state")
def _check_mail_unique(self):
for partner in self:
if partner.email and partner.membership_state != "none":
member_found = self.search(
[
("email", "=ilike", partner.email),
("id", "!=", partner.id),
("membership_state", "!=", "none"),
],
limit=1,
)
if member_found:
raise ValidationError(
self.env._(
"Another Member already exists with email %s", partner.email
)
if not partner.email or partner.membership_state == "none":
continue

# Use a direct SQL query to avoid triggering recursive Odoo search/constrain loops
self.env.cr.execute(
"""
SELECT id FROM res_partner
WHERE email ILIKE %s
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SQL condition email ILIKE %s treats % and _ in the provided email as wildcards (e.g., underscores are common in emails). That can cause false positives compared to Odoo's =ilike domain operator (which escapes wildcards for exact matching). Consider switching to an equality-based comparison such as LOWER(email) = LOWER(%s) (or explicitly escaping LIKE wildcards and adding an ESCAPE clause) to preserve exact-match semantics.

Suggested change
WHERE email ILIKE %s
WHERE LOWER(email) = LOWER(%s)

Copilot uses AI. Check for mistakes.
AND id != %s
AND membership_state != 'none'
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This direct SQL query bypasses Odoo's usual active_test filtering. Previously, self.search(...) would (by default) ignore archived partners, but the SQL will match inactive records too, potentially blocking reuse of an email that only exists on an archived member. If the old behavior is desired, add an AND active (or equivalent) predicate to the SQL to align with the ORM search semantics.

Suggested change
AND membership_state != 'none'
AND membership_state != 'none'
AND active

Copilot uses AI. Check for mistakes.
LIMIT 1
""",
(partner.email, partner.id),
)

if self.env.cr.fetchone():
raise ValidationError(
self.env._(
"Another Member already exists with email %s", partner.email
)
user_found = self.env["res.users"].search(
[
("login", "=ilike", partner.email),
("partner_id", "!=", partner.id),
],
limit=1,
)
if user_found:
raise ValidationError(
self.env._(
"Another User already exists with email %s", partner.email
)
user_found = self.env["res.users"].search(
[
("login", "=ilike", partner.email),
("partner_id", "!=", partner.id),
],
limit=1,
)
if user_found:
raise ValidationError(
self.env._(
"Another User already exists with email %s", partner.email
)
)

@api.model
def cleanup_unverified_members(self):
Expand Down
Loading