feature: mail templates (registry, DB-backed content, sandboxed Jinja2, API) #6

Merged
Gilles merged 2 commits from feature/mail-templates into master 2026-10-25 22:06:14 +00:00
Owner

Closes #5.

Summary

  • poulpinou.registry: static declaration of mail types (key, label, variables, default subject/body) by host apps, plus global variables merged into every render.
  • MailTemplate/MailTemplateImage/MailTemplateAttachment: the actual, DB-editable content per key, seeded from the registry on first access.
  • poulpinou.rendering: sandboxed Jinja2 (ImmutableSandboxedEnvironment, no Loader, soft-undefined so an unknown/removed variable renders blank, including inside {% for %}) -- supports {% if %}/{% for %}, same {{ var }} syntax as drh's current templates for a later read-based migration. HTML body autoescaped, subject not.
  • poulpinou.sending.send_mail_type(key, recipient, context): renders + attaches + sends.
  • Optional [api] extra (djangorestframework): list/detail/patch/reset + image/attachment upload endpoints, permission classes configurable via POULPINOU_MAIL_TEMPLATE_PERMISSION_CLASSES.

Migrating drh itself onto this is a separate, later PR in ws-backend -- out of scope here.

Test plan

  • tox -e py313 (pytest, 37 tests: registry, rendering incl. sandbox/autoescape/if-for, models, sending, API)
  • tox -e lint (pylint 10/10)
  • tox -e bandit (no issues)
  • tox -e format-check
  • CI (all Python versions) -- pending

🤖 Generated with Claude Code

https://claude.ai/code/session_013EeueXTWCT4Y6EbeideZcm

Closes #5. ## Summary - `poulpinou.registry`: static declaration of mail types (key, label, variables, default subject/body) by host apps, plus global variables merged into every render. - `MailTemplate`/`MailTemplateImage`/`MailTemplateAttachment`: the actual, DB-editable content per key, seeded from the registry on first access. - `poulpinou.rendering`: sandboxed Jinja2 (`ImmutableSandboxedEnvironment`, no `Loader`, soft-undefined so an unknown/removed variable renders blank, including inside `{% for %}`) -- supports `{% if %}`/`{% for %}`, same `{{ var }}` syntax as drh's current templates for a later read-based migration. HTML body autoescaped, subject not. - `poulpinou.sending.send_mail_type(key, recipient, context)`: renders + attaches + sends. - Optional `[api]` extra (djangorestframework): list/detail/patch/reset + image/attachment upload endpoints, permission classes configurable via `POULPINOU_MAIL_TEMPLATE_PERMISSION_CLASSES`. Migrating drh itself onto this is a separate, later PR in ws-backend -- out of scope here. ## Test plan - [x] `tox -e py313` (pytest, 37 tests: registry, rendering incl. sandbox/autoescape/if-for, models, sending, API) - [x] `tox -e lint` (pylint 10/10) - [x] `tox -e bandit` (no issues) - [x] `tox -e format-check` - [ ] CI (all Python versions) -- pending --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013EeueXTWCT4Y6EbeideZcm
feature: mail templates (registry, DB-backed content, sandboxed Jinja2, API)
All checks were successful
ci / test (3.11) (pull_request) Successful in 36s
ci / test (3.15) (pull_request) Successful in 41s
ci / lint (pull_request) Successful in 1m8s
ci / publish (pull_request) Has been skipped
ci / test (3.10) (pull_request) Successful in 36s
ci / test (3.12) (pull_request) Successful in 38s
ci / test (3.13) (pull_request) Successful in 37s
ci / test (3.14) (pull_request) Successful in 41s
b6ea070f1a
Generalizes the EmailTemplate pattern hardcoded in drh's apps.events
into a reusable poulpinou feature:

- poulpinou.registry: static, code-side declaration of mail types
  (key, label, variables, default subject/body), registered by host
  apps; plus global variables merged into every render.
- MailTemplate/MailTemplateImage/MailTemplateAttachment: the actual,
  DB-editable subject/body/attachments per key, seeded from the
  registry on first access.
- poulpinou.rendering: sandboxed Jinja2 (ImmutableSandboxedEnvironment,
  no Loader, soft-undefined so an unknown/removed variable renders
  blank instead of raising) -- supports {% if %}/{% for %}, same
  {{ var }} syntax as drh's current templates so a later read-based
  migration doesn't need template rewrites. HTML body is autoescaped,
  subject is not.
- poulpinou.sending.send_mail_type(key, recipient, context): renders,
  attaches, sends.
- Optional [api] extra (djangorestframework): list/detail/patch/reset
  endpoints + image/attachment upload, permission classes configurable
  via POULPINOU_MAIL_TEMPLATE_PERMISSION_CLASSES.

Closes #5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EeueXTWCT4Y6EbeideZcm
Author
Owner
<!-- argus-link-bot:Gilles/poulpinou-django --> 📊 [Rapports Argus pour cette PR](https://reports.stationmyr.net/Gilles/poulpinou-django/pr/6)
Merge branch 'master' into feature/mail-templates
All checks were successful
ci / test (3.10) (pull_request) Successful in 38s
ci / test (3.11) (pull_request) Successful in 36s
ci / test (3.12) (pull_request) Successful in 42s
ci / test (3.13) (pull_request) Successful in 40s
ci / test (3.14) (pull_request) Successful in 42s
ci / test (3.15) (pull_request) Successful in 41s
ci / lint (pull_request) Successful in 1m9s
ci / publish (pull_request) Has been skipped
39b038bfdc
Gilles scheduled this pull request to auto merge when all checks succeed 2026-10-25 22:06:03 +00:00
Gilles merged commit 9efcfbb96a into master 2026-10-25 22:06:14 +00:00
Gilles deleted branch feature/mail-templates 2026-10-25 22:06:14 +00:00
Sign in to join this conversation.
No description provided.