Problem
res.partner.birthdate (spp_registry/models/individual.py) accepts dates in the future when set through any path other than the web form:
- direct ORM calls —
partner.write({"birthdate": date(2030, 1, 1)})
create() vals
- CSV/Excel import
- API calls (XML-RPC, API v2, DCI endpoints)
The only existing guard is _birthdate_onchange, which runs exclusively in the form UI (it resets the field and shows a warning). Onchange never executes for ORM/import/API writes, so a future birthdate persists.
Once stored, the non-stored computed age field (compute_age_from_dates: relativedelta(now, dob)) yields negative years and renders as a negative string such as "-3" in views, exports, and anything else reading age.
Background
Pre-existing behavior, documented as out of scope in PR #357. That PR removed a dead @api.constrains("age") guard that looked like it covered this but never executed (constrains hooks don't fire on non-stored computes — the removal is behavior-preserving). No @api.constrains on birthdate exists anywhere in the repo; the only related check is registration_date >= birthdate in spp_registry/models/registrant.py, which does not reject future birthdates.
Proposed fix
Add to spp_registry/models/individual.py:
@api.constrains("birthdate")
def _check_birthdate_not_future(self):
for record in self:
if record.birthdate and record.birthdate > fields.Date.today():
raise ValidationError(_("Date of birth cannot be in the future."))
birthdate is a stored, writeable field, so the constraint fires on every create/write path and produces no registry-load warning.
Considerations
- Existing bad data: a constraint only validates on write; records that already hold a future birthdate stay invalid until touched — and would then block otherwise-unrelated writes. Pair the fix with a data-quality check (or migration note) for deployed databases.
birthdate_not_exact ("Approximate Birthdate"): presumably even an approximate birthdate must not be in the future, but confirm as a product decision.
_birthdate_onchange: keep it — the silent-reset UX in the form is friendlier than a raised ValidationError; the constraint is the server-side backstop.
- Follow the standard fix process: version bump +
readme/HISTORY.md fragment + README regen, and tests covering create/write/import paths (TDD: failing test first).
Problem
res.partner.birthdate(spp_registry/models/individual.py) accepts dates in the future when set through any path other than the web form:partner.write({"birthdate": date(2030, 1, 1)})create()valsThe only existing guard is
_birthdate_onchange, which runs exclusively in the form UI (it resets the field and shows a warning). Onchange never executes for ORM/import/API writes, so a future birthdate persists.Once stored, the non-stored computed
agefield (compute_age_from_dates:relativedelta(now, dob)) yields negative years and renders as a negative string such as"-3"in views, exports, and anything else readingage.Background
Pre-existing behavior, documented as out of scope in PR #357. That PR removed a dead
@api.constrains("age")guard that looked like it covered this but never executed (constrains hooks don't fire on non-stored computes — the removal is behavior-preserving). No@api.constrainsonbirthdateexists anywhere in the repo; the only related check isregistration_date >= birthdateinspp_registry/models/registrant.py, which does not reject future birthdates.Proposed fix
Add to
spp_registry/models/individual.py:birthdateis a stored, writeable field, so the constraint fires on every create/write path and produces no registry-load warning.Considerations
birthdate_not_exact("Approximate Birthdate"): presumably even an approximate birthdate must not be in the future, but confirm as a product decision._birthdate_onchange: keep it — the silent-reset UX in the form is friendlier than a raised ValidationError; the constraint is the server-side backstop.readme/HISTORY.mdfragment + README regen, and tests covering create/write/import paths (TDD: failing test first).