Skip to content
Snippets Groups Projects

parcours

Fast-Track the bureaucracy of legislative bodies.

Development Environment

# start development environment
make dev

# start production-like environment
make prod

# migrate database (requires running server)
make migrate

Once the server is started you can access the development environment on localhost:8537.

Other make targets:

  • lint: Checks code for formatting issues.
  • style: Formats the code according to the styleguide.
  • i18n: Updates .po translation files (Django’s makemessages).

New Releases

There are a few steps to follow for a new release:

  1. check the application in the staging area according to our test plan
  2. triage all OSV scanner results (visible in CI as security::osv-scan-container-image and security::osv-scan-dependencies jobs)
  3. bump the release version (see below).

We use bump-my-version for releasing new versions.

# new major release
poetry run bump-my-version bump major
# new minor release
poetry run bump-my-version bump minor
# new patch release
poetry run bump-my-version bump patch

# show which version number bump-my-version would set
poetry run bump-my-version show-bump

# simulate a release
poetry run bump-my-version bump major --dry-run --allow-dirty --verbose

After the changes and the tag created by bump-my-version have been pushed the CI will build a new container image and automatically publish the new version as a release in GitLab.

Deployment

Parcours can be deployed from source and via a container image. Only container image deployments are tested in production. We encourage you to use podman as container executor.

Container Image

We publish docker and podman compatible container images for parcours.

A stable release image can be found at git-registry.hack-hro.de:443/rokoli/parcours:latest.

An unstable rolling release image can be found at git-registry.hack-hro.de:443/rokoli/parcours:rolling.

We also publish container images for each tagged release. A list of container images can be found in the container registry.

Administrators should bind a volume or filesystem path to /var/lib/parcours, which is used as directory for dynamic data. Data is stored with uid/gid 220/220.

Database

We officially support PostgreSQL, even though other engines might be supported in Django (notably SQLite).

We recommend that you set an appropriate default ICU collation that enables natural sort for the database so that fields that make frequent use of numbers and are commonly sorted by (like Drucksache) are sorted like users expect them to. See docker/db.Dockerfile for an appropriate example.

Web server

We have only tested NGINX in production.

You can find example configuration files in docker/nginx-app.conf and docker/nginx-server.conf.

Be sure to configure proper websocket handling if you want to make use of parcours auto-sync capabilities.

Application environment variables

parcours mostly uses environment variables for configuration. Environment variables marked with ❗ must be set. Those marked with 👀 should at least be inspected.

PARCOURS_DATA_DIR 👀 : A directory path that is used as storage for dynamic data.

PARCOURS_SECRET_KEY ❗ : A password-like string with high entropy. This is used to encrypt sessions.

PARCOURS_DEBUG : Enable debug mode by setting this to yes.

PARCOURS_ALLOWED_HOSTS ❗ : The hostname parcours operates under (e.g. parcours.example.org). Multiple entries can be separated with a comma.

PARCOURS_CSRF_TRUSTED_ORIGINS : Trusted origins for CSRF tokens (including protocol and port, like https://parcours.example.org). Defaults to the list of allowed hosts above prefixed with https://.

PARCOURS_DATABASE_URL ❗ : Database URL for accessing the database. Format is specified in the dj_database_url documentation. Be advised that we only officially support PostgreSQL.

PARCOURS_EMAIL_DEFAULT_FROM 👀 : Default mail address for the From header used in emails sent by parcours.

PARCOURS_EMAIL_SMTP 👀 : The mail server used to send emails. Must be specified as a URL. Examples: smtp+tls://user:pw@mail.example.org:465?timeout=10 smtp+starttls://user:pw@mail.example.org:587

PARCOURS_SESSION_TIMEOUT_SECONDS 👀 : Controls the lifetime of a session. Defaults to 2 weeks. If you want users to be logged out faster, change it to a lower value (e.g. 86400 for a day).

PARCOURS_CHANNEL_LAYER 👀 : Used to enable cross-client communication. Must be specified as a URL to a Redis server (like redis://my.redis.host:6379/0).

PARCOURS_ENVIRONMENT 👀 : A custom free-form environment name, used in debugging messages.

PARCOURS_SENTRY_DSN 👀 : A Sentry DSN URL that can be used to collect error reports.

Application server environment variables

Parcours uses uvicorn as application server. uvicorn comes with a multitude of configuration options, a few of which can be set with environment variables.

This includes:

FORWARDED_ALLOW_IPS 👀 : Controls trusted proxies allowed to proxy traffic to the application server.

WEB_CONCURRENCY 👀 : Controls the number of workers used to handle incoming requests. Defaults to 1. You likely want to increase this.

See the uvicorn settings documentation for further and more extensive information.

Django Python configuration

You can put a custom Django configuration with the name parcours_settings.py in /etc/parcours on the filesystem. It will be loaded automatically.

LDAP integration

LDAP support is built-in and is based on the django-python3-ldap module. All configuration options from the django-python3-ldap module documentation can be used but require the use of the Django Python configuration.

Example configuration

# Enable the LDAP authentication backend.
AUTHENTICATION_BACKENDS = [
    "django_python3_ldap.auth.LDAPBackend",
    # You might omit the ModelBackend if users should only be able
    # to authenticate via LDAP. This will also disable custom admin accounts
    # that have been created using the createsuperuser command.
    "django.contrib.auth.backends.ModelBackend",
]

LDAP_AUTH_URL = "ldaps://my-ldap-server.example.org:636"
LDAP_AUTH_USER_TLS = True
LDAP_AUTH_SEARCH_BASE = "cn=users,dc=example,dc=org"
LDAP_AUTH_OBJECT_CLASS = "posixAccount"
LDAP_AUTH_USER_LOOKUP_FIELDS = ("username",)
LDAP_AUTH_USER_FIELDS = {
    "username": "uid",
    "email": "mail",
    "name": "displayName",
}