Skip to content

Local development

Local development

Useful commands

tasks.py defines invoke tasks for common jobs. List them with:

inv -l

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.

./manage.py createsuperuser

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:

./manage.py promote_user_to_superuser <email>

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>):

./manage.py generate_sample_data 50 demo-org

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:

./manage.py generate_microplanning_data --fake-hq

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:

./manage.py run_fake_hq

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.app in your .env file

Create an OAuth2 application on CommCare HQ

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

Test the OAuth2 flow

  • Confirm COMMCARE_HQ_URL points at https://staging.commcarehq.org. That is the default, so you only need to set it in .env if 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:

celery -A config.celery_app worker -l info

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:

celery -A config.celery_app beat

or embed the beat service inside a worker with the -B option (not recommended for production use):

celery -A config.celery_app worker -B -l info

Tests

Run the test suite:

pytest

To check your test coverage and generate an HTML coverage report:

coverage run -m pytest
coverage html

Then open htmlcov/index.html.

Live reloading and Sass CSS compilation

inv build-js -w

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