Filed at origin/19.0 (23f43963). From the expert review of #477.
Summary
spp_change_request_v2 guards accesses to res.partner registry fields with hasattr(), but spp_registry is a hard dependency (__manifest__.py → depends), so those fields always exist. The guards are dead code, and they read as defensive in places where the actual failure mode was a hard crash one line inside them — #476 was exactly that: hasattr(registrant, "group_membership_ids") passed, then the very next line raised KeyError: 'individual_id'.
Sites (all on origin/19.0):
| Location |
Guard |
Field defined at |
models/conflict_mixin.py:280 |
hasattr(registrant, "group_membership_ids") |
spp_registry/models/group.py:49 |
models/conflict_mixin.py:287 |
hasattr(registrant, "individual_membership_ids") |
spp_registry/models/individual.py:48 |
models/change_request.py:604 |
hasattr(reg, "group_membership_ids") |
as above |
models/res_partner.py:46 |
hasattr(partner, "reg_ids") |
spp_registry/models/registrant.py:35 |
models/res_partner.py:52 |
hasattr(partner, "group_membership_ids") |
spp_registry/models/group.py:49 |
wizards/create_wizard.py:193 |
hasattr(reg, "group_membership_ids") |
spp_registry/models/group.py:49 |
models/change_request.py:662, :1129 |
hasattr(record/self, "_run_conflict_checks") |
spp.change.request unconditionally inherits spp.cr.conflict.mixin (models/change_request.py:18-23) |
One guard is worse than dead — it is always False
models/change_request.py:585:
if hasattr(reg, "spp_id") and reg.spp_id:
escaped_id = html_escape(reg.spp_id)
html_parts.append(f'<div class="mb-2"><span class="badge bg-secondary">ID: {escaped_id}</span></div>')
No module in this repo defines spp_id on res.partner (grep -rn "spp_id = fields" → no hits repo-wide). The ID badge in registrant_summary_html therefore never renders, and the XSS regression test written for it passes trivially — test_registrant_summary_escapes_spp_id (tests/test_html_escaping.py:56-71) wraps its entire body, assertions included, in if hasattr(registrant, "spp_id"), which is never true.
Two related loose ends turned up while checking this, worth a look but not part of this cleanup:
spp_api_v2/routers/consent.py:236,238 read signatory_id.spp_id / group_id.spp_id unguarded — a latent AttributeError on that path if the field really does not exist there.
spp_change_request_v2/static/src/components/review_panel/review_panel.xml:89-92 renders state.registrantData.spp_id, which the same absence would make permanently empty.
Whoever picks this up should decide whether spp_id is meant to exist (add the field / bridge it) or whether these are leftovers from an earlier data model, and then either fix the branches or delete them.
Keep
models/change_request.py:862 — hasattr(detail, "prefill_from_registrant"): detail models are contributed by other modules and genuinely vary. Real duck-typing.
models/conflict_mixin.py:616 — hasattr(value, "ids"): a recordset-vs-scalar check on a field value. Real duck-typing.
Filed at
origin/19.0(23f43963). From the expert review of #477.Summary
spp_change_request_v2guards accesses tores.partnerregistry fields withhasattr(), butspp_registryis a hard dependency (__manifest__.py→depends), so those fields always exist. The guards are dead code, and they read as defensive in places where the actual failure mode was a hard crash one line inside them — #476 was exactly that:hasattr(registrant, "group_membership_ids")passed, then the very next line raisedKeyError: 'individual_id'.Sites (all on
origin/19.0):models/conflict_mixin.py:280hasattr(registrant, "group_membership_ids")spp_registry/models/group.py:49models/conflict_mixin.py:287hasattr(registrant, "individual_membership_ids")spp_registry/models/individual.py:48models/change_request.py:604hasattr(reg, "group_membership_ids")models/res_partner.py:46hasattr(partner, "reg_ids")spp_registry/models/registrant.py:35models/res_partner.py:52hasattr(partner, "group_membership_ids")spp_registry/models/group.py:49wizards/create_wizard.py:193hasattr(reg, "group_membership_ids")spp_registry/models/group.py:49models/change_request.py:662,:1129hasattr(record/self, "_run_conflict_checks")spp.change.requestunconditionally inheritsspp.cr.conflict.mixin(models/change_request.py:18-23)One guard is worse than dead — it is always False
models/change_request.py:585:No module in this repo defines
spp_idonres.partner(grep -rn "spp_id = fields"→ no hits repo-wide). The ID badge inregistrant_summary_htmltherefore never renders, and the XSS regression test written for it passes trivially —test_registrant_summary_escapes_spp_id(tests/test_html_escaping.py:56-71) wraps its entire body, assertions included, inif hasattr(registrant, "spp_id"), which is never true.Two related loose ends turned up while checking this, worth a look but not part of this cleanup:
spp_api_v2/routers/consent.py:236,238readsignatory_id.spp_id/group_id.spp_idunguarded — a latentAttributeErroron that path if the field really does not exist there.spp_change_request_v2/static/src/components/review_panel/review_panel.xml:89-92rendersstate.registrantData.spp_id, which the same absence would make permanently empty.Whoever picks this up should decide whether
spp_idis meant to exist (add the field / bridge it) or whether these are leftovers from an earlier data model, and then either fix the branches or delete them.Keep
models/change_request.py:862—hasattr(detail, "prefill_from_registrant"): detail models are contributed by other modules and genuinely vary. Real duck-typing.models/conflict_mixin.py:616—hasattr(value, "ids"): a recordset-vs-scalar check on a field value. Real duck-typing.