Kausal Watch is a service for administrating and monitoring action plans. It has the following components:
- admin UI for modifying action plan content
- REST API for distributing the information
The service was first used to implement monitoring for the Carbon-neutral Helsinki 2035 action plan. The ReactJS UI code is also open source.
Build the Docker containers first with:
docker compose buildThen start them with:
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d appThis should start an HTTP server on localhost, port 8000. To be able to log in,
you'll need to create a user together with some test data:
docker compose exec app ./manage.py create_superuser_with_defaults \
--email superuser@example.com --organization "Test org" \
--first-name Super --last-name UserAfter the command above finishes, you can point your browser at localhost:8000 and log in using the password you provided.
Initialize the required shared-code submodule. It also provides the mise configuration shared
with Kausal Paths through the symlinks in mise/:
git submodule update --init kausal_commonIn the project root directory, create and activate a Python virtual environment:
uv venv
source .venv/bin/activateInstall the required Python packages:
uv syncThe Django apps live under src/ (a src layout), and uv sync installs the project itself in
editable mode, which is what puts src/ on the import path. The apps are enumerated in
[tool.uv.build-backend] module-name in pyproject.toml, so a newly added app has to be listed
there as well. manage.py, conftest.py, templates/ and locale/ stay at the repo root.
If you have access to the Kausal private extensions, check out the optional submodule.
A plain git submodule update --init skips it on purpose, so it needs an explicit --checkout:
git submodule update --init --checkout private/extensionsThe kausal_watch_extensions package then becomes importable through the committed
src/kausal_watch_extensions symlink. When the submodule is absent, the symlink dangles and
the extension is simply not installed.
Create a .env file in your repo root with the following contents. Ask a teammate for the values of AZURE_AD_ variables.
DEBUG=1
DATABASE_URL=postgis:///aplans
AZURE_AD_CLIENT_ID=
AZURE_AD_CLIENT_SECRET=
Build the Kausal extensions (only relevant when the submodule is checked out):
mise depsThis installs the extension client's Node dependencies, builds its bundles into the extension's
static/ directory, and compiles its translations. The provider configuration lives in the
submodule (private/extensions/mise/watch.toml) and is loaded through mise/conf.d/. Whenever a
submodule update changes the client sources or translation files, mise reminds you to rerun it
and mise run does so automatically.
Collect static files:
python manage.py collectstaticMake sure you have created a Postgres database with the same name (here aplans).
Run migrations:
python manage.py migrateCreate a superuser:
Note: You might need the following translations during the createsuperuser operation: käyttäjätunnus = username, sähköpostiosoite = e-mail
python manage.py createsuperuserTo access the admin UI with the created superuser, create and associate a Person with it:
python manage.py shell_plussuperuser = User.objects.get(email='<email of the superuser you created>')
organization = Organization.objects.get(
abbreviation='Kausal'
) # Found only if database is prepopulated with the help of a coworker
person = Person.objects.create(
user=superuser,
first_name='<first name of your user>',
last_name='<last name of your user>',
email='<email of the superuser you created>',
organization=organization,
)
person.save()Compile the translation files:
python manage.py compilemessagesRun the development server, the Admin UI will be available at localhost:8000:
python manage.py runserverNote: the database will be empty, ask a teammate for help to restore your local database from a backup
The project is containerized using Docker Compose. You will still need to set some
variables in your environment; see the first few lines in src/aplans/settings.py.
In particular, you will need to set the database credentials; for example:
POSTGRES_PASSWORD=change_me
DATABASE_URL=postgis://watch:change_me@db/watch
We use uv to manage dependencies. Invoke uv sync -P <PACKAGE> to upgrade one package,
and uv sync -U to upgrade all of them.
To extract translatable strings and update every translation catalogue this repo owns, run:
mise run messages
This updates the Watch catalogues in locale/ (the django, djangojs and notifications
domains), the shared kausal_common/locale/, and, when the extensions submodule is checked
out, the catalogue of the package named by extension_package in mise.dev.toml.
mise run messages-host, messages-common and messages-extensions update one of them at a time.
The tasks come from the shared mise config in kausal_common and all run
kausal_common/development/tools/makemessages.sh.
Do not run a bare python manage.py makemessages from the repo root. Django walks every
directory under the current one, so it would also rewrite the catalogues of kausal_common
and the extensions submodule, and send strings from their shared modules into locale/. The
script instead scopes each run to its own catalogue, and extracts kausal_common and the
extensions from inside their own directories so that Watch and Paths produce identical
catalogues for them.
Only languages that already have a catalogue are updated. To add a language, create its
catalogue once by hand, for example
python manage.py makemessages -l de --add-location=file --no-wrap --ignore private --ignore kausal_common --ignore node_modules.
The notification templates (.mjml) use Jinja2 rather than the Django template language,
so their notifications domain is extracted with pybabel as configured in babel.cfg.
To create a new message catalog (.po file) from the generated .pot file, you can run the following (example for the de locale):
pybabel init -D notifications -i locale/notifications.pot -d locale -l de
The equivalent of compilemessages for the MJML templates is the following (example for the de locale):
pybabel compile -D notifications -d locale -l de