Story + calls-to-action app for volunteer orgs. Campaign pages where money is ONE way to help: give (wrapped Givebutter widget, or SimpleTip), mentor, hire, host an event, AMA, subscribe. Testimonials from named-or-anonymous vouchers (LinkedClaim URI per testimony, publish-gated), per-person share links with counted attribution, Instagram story/post card downloads.
First deployment target: VM 513 (voluntask), domain act.raisethevoices.org, for the
Raise the Voices JREAS Hub campaign. Design + phased plan: projects repo
Active/raise-the-voices/act-design.md; VM handoff: dashboard-vm513-handoff.md.
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python manage.py migrate
.venv/bin/python manage.py seed_jreas # RTV org + JREAS campaign (draft)
.venv/bin/python manage.py createsuperuser
.venv/bin/python manage.py runserverPublish the campaign in /admin (Campaigns → jreas → status: published).
Live: cooperation.org/letters/ on the civic-actions VM (10.0.0.154) since 2026-09-07; only
/letters/, /admin/, /static/, /media/ are routed there. Later: act.raisethevoices.org
(VM 513) for the campaign pages.
.envfrom.env.example(mode 600). Postgres: databaseacton VM 100 (rolesact_ownerfor migrations,act_userfor the running app).ACT_BASE_PATH/SCRIPT_NAMEempty at a domain root.- Python 3.12 venv,
pip install -r requirements.txt. WeasyPrint needslibpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0. manage.py migrate --check(runmigratewith theact_ownercredentials if pending),collectstatic.media/must be writable by the service user (act).deploy/act-retry-mail.service+.timer: confirmation-mail retries (systemctl enable --now act-retry-mail.timer).deploy/act.service(gunicorn 127.0.0.1:8050) anddeploy/nginx-cooperation.org.conf(locations added inside the existing cooperation.org vhost) are what is live.deploy/nginx-act.confis the own-domain variant for act.raisethevoices.org.- Deploy a change:
git pull,.venv/bin/pip install -r requirements.txt,collectstatic --clear(hashed filenames; nginx caches/static/for 7 days), tests withACT_DEBUG=1 manage.py test,migrate --check,sudo systemctl restart act. - DNS + Caddy route are host-side actions. Register in cobox
app-registry.md.
One repo and one database schema, several deployments. ACT_APPS in .env picks the apps a
deployment routes and shows in admin: campaigns (story + calls to action, owns /),
letters (open letters and petitions at /letters/). Org and volunteer accounts are shared
and always visible.
| Site | ACT_APPS | Public paths |
|---|---|---|
| cooperation.org | letters |
/letters/, /admin/ (root forwards to /letters/) |
| raisethevoices.org | campaigns |
/, /c/<slug>/, /admin/ |
New capabilities (content management, content planning, email campaigns) arrive as further apps in this list.
letters/ app, pages under /letters/<slug>/. A Letter belongs to an Org, is written in
Markdown (bold, italics, paragraphs, bullets), and is draft until published in /admin.
kind is open letter (counter only) or petition (addressee + goal). Theme: parchment,
EB Garamond (bundled, OFL).
Signing: first name, last name, city, country, plus the letter's own fields
(Letters → Signature fields: label, kind short / paragraph / checkbox, required, public).
A signature counts when the signer has either confirmed their email or drawn a
signature (signature_pad, stored as PNG). Email is required to get updates
(keep_updated). Same email twice: an unconfirmed one is replaced, a confirmed one is
refused.
Confirmation mail goes out over SMTP (EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD,
DEFAULT_FROM_EMAIL). With EMAIL_HOST unset nothing is sent; every attempt, sent or failed,
is an EmailLog row. A failed send is retried automatically 1 h, 6 h and 24 h later
(letters.models.RETRY_DELAYS; manage.py retry_confirmations on deploy/act-retry-mail.timer,
every 15 min), then it gives up. The schedule and last error live on the Signature. Admin →
Signatures → filter "confirmation": confirmed / drawn / approved / sent, waiting / failed, retry
scheduled / failed, gave up / no email sent; action "Resend confirmation email" starts over.
Approved (checkbox in the list) makes a signature count and show even if the email never
confirmed — for the friend who signed but never got the mail.
PDF: /letters/<slug>.pdf is the letter with every counted signature that has on_pdf
ticked, ordered by pdf_sort then date. In admin, filter (city, country, …), tick rows,
action "PDF of the letter with the selected signatures". WeasyPrint renders the same
templates; static and media files are read from disk.
act never touches funds. The give rail embeds the Givebutter widget for the org's
Givebutter account (charity of record: Civic Works — receipts and discretion-and-control
disclosure are locked text in campaigns/donation_text.py, deliberately not DB-editable;
see projects repo Active/raise-the-voices/legal/fundraising-compliance.md) or a
<simple-tip> element. Set Org.givebutter_account_id + Campaign.givebutter_campaign_id
in admin when the Givebutter campaign exists.
Volunteer logins get is_staff + a VolunteerProfile(org=...); admin queries are scoped to
their org, delete is superuser-only, publishing (campaigns, testimonials, updates) is an
approver action. LinkedTrust SSO activates when LINKEDTRUST_CLIENT_ID/SECRET are set
(client registered at live.linkedtrust.us — see django-linkedtrust-auth README); Django
session login always works as the fallback.