The repository holds the codebase of the main DDCS web application.
| Tool | Version | Notes |
|---|---|---|
| Python | 3.12 | Use pyenv or a system package |
| Node.js + npm | any current LTS | Required for CSS compilation |
| Git | any | Pre-commit hooks are used |
| gettext | any | Required for compiling translations |
PostgreSQL is only needed in production. Local development uses SQLite.
git clone https://github.com/Nico-AP/ddcs-app
cd ddcs-apppython -m venv .venv
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windowspip install -r requirements/dev.txtcp .env.example .envEdit .env and set at minimum:
DJANGO_SETTINGS_MODULE=config.settings.local
SECRET_KEY=<any-random-string> # generate with: python -c "import secrets; print(secrets.token_urlsafe(50))"
All other variables have sensible local defaults. Email is printed to the console and the database is SQLite — no further configuration is needed for local development.
pre-commit install
pre-commit install --hook-type pre-push # enables pip-audit on pushpython manage.py migratepython manage.py createsuperuserpython manage.py compilemessagescd .sass && npm install && cd ..Start the CSS watcher so styles rebuild on save:
cd .sass && npm run sass:watchpython manage.py runserverThe app is now available at http://127.0.0.1:8000. The Django admin is at the URL set by ADMIN_URL (default: admin/).
We work with three central branches:
dev: Shared code, pre-deployment;stage: Deployed to stage for testing and QA before production.main: Deployed to production.
All three branches are protected: no direct pushes, all changes land via PR (with CI checks passing and branches required to be up to date before merge).
- Create local branch (e.g.,
feat/something-nice/refactor/reportsetc.) fromdev:
git checkout dev
git pull origin dev
git checkout -b feat/something-nice- Work and commit locally on
feat/something-nice
git add .
git commit -m "feat: app now talks to ghosts"- When the new code is ready to be integrated into the shared codebase, ensure
it is still in sync with the shared codebase on
devand resolve conflicts if necessary. Also do this regularly (e.g., daily) if work is carried out over a longer time.
git checkout dev
git pull origin dev
git checkout feat/something-nice
git merge dev # or rebase
# resolve conflicts and commit again if needed.- Push your local branch to the repository:
git push -u origin feat/something-nice-
Go to github.com and open a Pull Request (PR; through "Compare & pull request").
-
In the web UI, Review the diff yourself, or tag someone else to review it. Wait for CI (lint, tests) to pass.
-
In the web UI, merge PR (use squash)
-
Back in your IDE, sync and clean up:
git checkout dev
git pull origin dev
git branch -d feat/something-nice
git push origin --delete feat/something-niceOnce one or more updates on dev are ready to be tested on the server,
promote dev to stage.
-
Announce in the chat that you're promoting to stage (so the other person isn't surprised by a deploy).
-
Promote
devtostagethrough pull request:
git checkout dev
git pull origin dev
git push origin dev # ensures remote dev is current
gh pr create --base stage --head dev --title "Promote dev to stage: $(date +%Y-%m-%d)"
# review diff in browser, wait for CI, merge (use 'merge commit')-
Run the stage deployment playbook from the ddcs-infra repo.
-
Test updates on the stage server. Fix bugs found here as normal commits on
dev(via afix/...branch and PR, same as any other change), then repeat steps 2–3 to re-promote.
stage is protected: no direct pushes, only merges via PR from dev or hotfix/*
(see below).
Every production release gets a tag on main, created right after the
promotion PR merges and before deployment. The tag is required by the
ddcs-infra playbook that handles the production deployment -
it uses the tag to pull the correct code version.
Tags are a plain, incrementing counter (r1, r2, r3, ...).
Tags are important for:
- Knowing what is deployed — production deployments are always linked to
a specific tag/release, never
maindirectly. So what's on prod is exactly what we decided to ship, even ifmainmoves forward again before the deploy runs. - Rollback target — redeploy a known-good tag without hunting for the right commit SHA.
- Reference point for debugging — "this broke after r10" is a lot more useful than "sometime last week."
- Changelog for free —
git log r9..r10shows exactly what shipped in a release, assuming PR titles follow commit hygiene below.
Once stage has been tested and is ready for release:
-
Announce in chat that you're releasing to production, and — if not already known — what the update includes.
-
Find the latest release tag, to name the PR (the actual tag is created later, in step 4):
git tag --list 'r[0-9]*' | sort -V | tail -1This returns, e.g., r10. This means the next release will be r11.
- Promote
stagetomainthrough pull request:
git checkout stage
git pull origin stage
gh pr create --base main --head stage --title "Release r11"
# review diff, wait for CI, merge (merge commit)- After merging the PR int main, tag the release:
git checkout main
git pull origin main
git tag r11
git push origin --tags-
Run the production deployment playbook from the ddcs-infra repo, targeting
r11. -
Confirm the app is healthy on prod (quick smoke check).
main is protected: no direct pushes, only merges via PR from stage or
hotfix/*.
For urgent production bugs that can't wait for the normal dev → stage → main cycle:
- Branch off
maindirectly:
git checkout main
git pull origin main
git checkout -b hotfix/short-description-
Fix, commit, push, open a PR into
main(same PR flow as normal development, base =maininstead ofdev). -
Merge the PR using merge commit, not squash, to match
main's convention. Then tag and deploy to production as in "Promoting to Production" above (find next tag, tag, deploy targeting the tag, smoke check, log the release). -
Important: Merge the fix back into
stageanddevso they don't drift out of sync withmain:
gh pr create --base stage --head main --title "Sync hotfix into stage"
# review diff, wait for CI, merge (use merge commit, not squash)
git checkout dev
git pull origin dev
gh pr create --base dev --head stage --title "Sync hotfix into dev"
# review diff, wait for CI, merge (use merge commit, not squash)Feature branches: commit however works for you — messy, frequent,
"wip"/"fix"/"actually fix" is fine. These commits get squashed away and
never appear in dev history, so there's no need to keep them clean.
PR titles matter — since squash merge uses the PR title as the
final commit message on dev, the PR title is the one thing that becomes
permanent history. Write it as a semantic commit:
<type>(<apps it touches>): <short description>
Common types:
| Type | Use for |
|---|---|
feat |
new functionality |
style |
visual/UI-only change, no logic change |
fix |
bug fix |
refactor |
code change with no behavior change |
chore |
tooling, deps, config, non-code maintenance |
docs |
documentation only |
Examples:
feat(metadata): add scraperstyle(reports): update button stylingfix(website): missing closing tag in hero templatechore: update sass scriptrefactor(reports): move metrics into dedicated package
Keep the description short and in the imperative mood ("add X", not "added X"
or "adds X"). This keeps git log on dev/main readable as a changelog
and makes git bisect and tag-range diffs (git log r9..r10) actually useful.
python -m coverage run manage.py test
python -m coverage report # fails if coverage drops below 80 %Usually, auto-run when committing, but good to know:
ruff check . # lint
ruff check . --fix # lint and auto-fix
ruff format . # format
djlint ddcs/ --check # check templates
djlint ddcs/ --reformat # reformat templatescd .sass && npm run sass:buildOutputs a compressed stylesheet without source maps to ddcs/core/static/core/css/ddcs.css.
To skip a specific hook (e.g. when a vulnerability has no fix yet):
SKIP=pip-audit git pushTo skip all hooks — use sparingly:
git push --no-verifySKIP accepts a comma-separated list of hook IDs: SKIP=pip-audit,check-translations git push.
The site defaults to German (de-de). Source strings are written in English; the German .po file provides translations.
Python (views, models, forms):
from django.utils.translation import gettext_lazy as _
label = _("Your string here")Templates:
{% load i18n %}
{% trans "Your string here" %}
{# With variables #}
{% blocktrans with name=user.name %}Hello, {{ name }}{% endblocktrans %}After adding new strings:
# Extract new strings into the .po file (also removes obsolete entries)
python manage.py makemessages -l de --no-obsolete
# Translate the new entries in locale/de/LC_MESSAGES/django.po, then compile:
python manage.py compilemessagesThe pre-commit hook will block commits that introduce untranslated strings — it runs makemessages and fails if the .po file changes.
Stage the updated .po file together with your code changes.
Translation files are located at locale/de/LC_MESSAGES/django.po.
- Add the language to
LANGUAGESinconfig/settings/base.py - Run
python manage.py makemessages -l <language_code> - Translate strings in
locale/<language_code>/LC_MESSAGES/django.po - Run
python manage.py compilemessages