Local development
Local development¶
Useful commands¶
tasks.py defines invoke tasks for common jobs. List them with:
To add a Python dependency, use uv add <pkg>; see
Dependency management
for the details.
Setting up your users¶
You don't need the CommCare HQ social app to create local users. The "Log in with CommCare HQ" button stays
hidden until a commcarehq social app exists (see
Setting up auth with CommCare HQ).
To create a superuser, run the command below. --email is not accepted, because the custom User model sets
REQUIRED_FIELDS = []; set the address afterwards in the admin if you need one.
To create a normal user account, go to Sign Up and fill out the form. Local settings use
ACCOUNT_EMAIL_VERIFICATION = "optional", so you are logged straight in; the confirmation email is still printed to
the console if you want to verify the address.
To promote an existing user to superuser:
For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users.
Sample data¶
To populate a local database with organizations, opportunities, visits and payments
(generate_sample_data <num_visits> <org_slug>):
The organization is created if the slug does not already exist. Each run first deletes the existing
opportunities, visits, payments, programs and attachments belonging to the two organizations it is about to use,
including the attachment files under MEDIA_ROOT. Optional flags:
--invited_org_slug— reuse a specific organization as the invited org. Without it, a new invited org with a random slug is created on every run, and the previous one is left behind.--managed_opportunities— how many managed opportunities to create (default 3)--image_percent— percentage of visits that get a photo (default 0, none)--audio_percent— percentage of visits that get one to three audio recordings, each with a stand-in transcript and translation (default 0, none)--media_dir— a directory to search (recursively) for image / audio files to use as the attachments for generated visits if requested. Otherwise placeholders are used.
Adding yourself to the generated organizations is a separate step, either through
/admin/organization/userorganizationmembership/ or the organization's own member admin pages. Note there are
two: the primary org from <org_slug> and the invited org, which owns the managed opportunities — join only the
first and those stay hidden. Worker tabs list only workers whose OpportunityAccess is accepted, which the
generated ones are.
Microplanning, without CommCare HQ¶
To seed an opportunity with work areas, groups, workers and visits for the Progress Map:
It prints the Progress Map URL and creates the superuser mpadmin with password mpadmin. --help lists the
other options.
Assigning, unassigning, excluding and editing an assigned work area all update cases on CommCare HQ. --fake-hq
sends those calls to a stand-in for HQ on localhost:8001 and gives each worker an HQ user there. Run the
stand-in in its own terminal:
It answers every case update and prints the request with its body, so you can see what Connect sends. Start it
with --fail to answer case updates with an error instead. To see the push notification that assignment sends to
ConnectID too, start the Celery worker with CONNECTID_URL=http://localhost:8001/connectid.
Setting up auth with CommCare HQ¶
Expose your local service to the internet
- Create an account on ngrok and install ngrok
- Create a custom domain on ngrok using this link
- Run
ngrok http --url=[my-unique-subdomain].ngrok-free.app 8000 - Set
DJANGO_ALLOWED_HOSTS=[my-unique-subdomain].ngrok-free.appin your.envfile
Create an OAuth2 application on CommCare HQ
- Navigate to https://staging.commcarehq.org/oauth/applications/
- Create a new application with these settings:
| Setting | Value |
|---|---|
| Client Type | Confidential |
| Authorization Grant Type | Authorization Code |
| Redirect URIs | https://[my-unique-subdomain].ngrok-free.app/accounts/commcarehq/login/callback/ |
| Pkce required | False |
- Copy the Client ID and Client Secret
- Create a new SocialApp locally at http://localhost:8000/admin/socialaccount/socialapp/
Test the OAuth2 flow
- Confirm
COMMCARE_HQ_URLpoints athttps://staging.commcarehq.org. That is the default, so you only need to set it in.envif you have overridden it. - Navigate to http://[my-unique-subdomain].ngrok-free.app/accounts/login/
- Click the "Log in with CommCare HQ" button
- You should be redirected to CommCare HQ to log in
- After logging in, you should be redirected back to the app and logged in
Celery¶
Local settings run Celery tasks eagerly, inside the request (CELERY_TASK_ALWAYS_EAGER defaults to True in
config/settings/local.py), so you don't need a worker. That blocks the request and hides progress UIs, though.
To run tasks the way production does, set CELERY_TASK_ALWAYS_EAGER=False in .env and start a worker:
For Celery's import magic to work, run the commands from the folder that holds manage.py.
To run periodic tasks, you'll need to start the celery beat scheduler service. You can start it as a standalone process:
or embed the beat service inside a worker with the -B option (not recommended for production use):
Tests¶
Run the test suite:
To check your test coverage and generate an HTML coverage report:
Then open htmlcov/index.html.
Live reloading and Sass CSS compilation¶
Commands¶
# Local services (PostgreSQL/PostGIS + Redis)
inv up # docker compose up
inv down # docker compose down
# Django
./manage.py migrate # run migrations (dev)
./manage.py migrate_multi # run migrations on both primary + secondary DB (prod)
./manage.py runserver # dev server
./manage.py generate_microplanning_data --fake-hq # demo work areas; HQ calls go to run_fake_hq
./manage.py run_fake_hq # stand-in for CommCare HQ on :8001 that prints each request it gets
# JavaScript/CSS
npm ci # install deps
inv build-js # dev build
inv build-js -w # dev build with watch
inv build-js --prod # production build
npm run build # production build (what CI runs)
# Celery (local dev)
# Tasks run eagerly inside the request by default, so no worker is needed -- but that blocks the
# request and hides progress UIs. Set CELERY_TASK_ALWAYS_EAGER=False in .env and run a worker:
celery -A config.celery_app worker -B -l info
# Tests
pytest # run all tests
pytest path/to/test_file.py::test_name # run single test
# Duplicate code
jscpd --changed commcare_connect # clones in your uncommitted files, [NEW] = not in HEAD (pre-commit hook; settings in .jscpd.json)
# Linting (ruff, ruff-format, django-upgrade, prettier, djlint, eslint, jscpd)
prek run -a
# Requirements (uv)
uv sync
uv add <pkg>
# Translations
inv translations
# Developer docs site (Zensical; config in zensical.toml, pages in docs/)
uv run --group docs zensical serve # live preview on :8000
uv run --group docs zensical build # strict build into ./site, as CI does