Skip to content

Latest commit

 

History

History

backend

README.md

Backend

Django REST API, business logic, Django admin panel and Celery workers for the Request Manager.

Stack: Python · Django · Django REST Framework · Celery (Redis) · PostgreSQL · drf-spectacular · social-auth · Sentry

Setup

Requires uv, which installs the Python version pinned in .python-version, and a running PostgreSQL + Redis (start them from the repository root with docker compose -f docker-compose.dev.yaml up -d --wait).

cd backend
uv sync                        # install dependencies into .venv
cp .env.sample .env            # then edit it (see below)
uv run python manage.py migrate
uv run python manage.py seed_dev_data   # test data, see below
uv run python manage.py runserver

The API is now available at http://localhost:8000.

Environment

All settings are read from backend/.env. Start from .env.sample, which documents every variable and its default. For local development set:

DJANGO_SETTINGS_MODULE = core.settings.debug

Web push keys

Push notifications stay off until WEB_PUSH_CONTACT, WEB_PUSH_VAPID_PRIVATE_KEY and WEB_PUSH_VAPID_PUBLIC_KEY are all set. Generate one key pair per environment:

uv run vapid --gen
uv run vapid --applicationServerKey

--gen writes private_key.pem and public_key.pem; --applicationServerKey prints the value for WEB_PUSH_VAPID_PUBLIC_KEY. WEB_PUSH_VAPID_PRIVATE_KEY is the base64 body of private_key.pem on one line, without the BEGIN and END lines. Then delete both .pem files: git does not ignore them. Keep the pair once browsers have subscribed: a subscription only accepts pushes signed with the key it was made with.

Settings modules

DJANGO_SETTINGS_MODULE selects the configuration (core.settings.*):

Module Use
debug Local development (Django Debug Toolbar, console e-mail).
production Production default.
staging Staging environment.
ci / test Automated tests and CI.

Running the workers

Celery is used for asynchronous tasks and scheduled jobs:

uv run celery -A core worker    # task worker
uv run celery -A core beat      # scheduler

Testing

Tests run with pytest:

uv run pytest

Coverage must stay at or above 90% (fail_under = 90).

PyCharm: to use pytest instead of the Django test runner, enable Settings → Python → Django → Do not use Django test runner, and set the default test runner to pytest under Settings → Python → Tools → Integrated Tools.

Test data

seed_dev_data fills the database with invented people and requests, for manual testing and for the frontend's end-to-end tests. It first deletes every request and every user with an @example.com address. Users who logged in through single sign-on stay, and so do their groups. It asks before deleting; --no-input skips the question.

uv run python manage.py seed_dev_data
uv run python manage.py seed_dev_data --me <your username>   # your own account gets requests, crew spots, a video and todos too

The people include the admin admin.aladar and the requester minta.anna, who has most of the example requests. To use the app as one of them, create a session:

uv run python manage.py dev_session admin.aladar

It prints a line to paste into the browser console on https://localhost:5173. Paste it logged out or in a private window, because the page cannot overwrite the cookie of an existing login. Run it with the same settings and .env as the server, so that both use the same session store and secret key.

Both commands come only with the debug and test settings; production does not have them.

Code style

Formatting and linting are enforced by pre-commit (Black, isort, flake8, bandit, pyupgrade, django-upgrade). Install the hooks once from the repository root with pre-commit install.

Upgrading runtime versions

Renovate keeps most versions current automatically. A few values encode the minimum supported version and are intentionally left for you to bump by hand when you raise it — Renovate does not touch these floors:

When you upgrade… Also bump…
Python requires-python in pyproject.toml, and the pyupgrade --py<version>-plus arg in .pre-commit-config.yaml.
Django the django-upgrade --target-version arg in .pre-commit-config.yaml.

The runtime versions themselves live in .python-version (Python) and the .nvmrc files (Node) and are bumped by Renovate.

OpenAPI schema

The REST API is documented with an OpenAPI schema (backend/schema.yaml), which is also the source for the frontend's generated API client. Regenerate it after changing serializers, views or endpoints:

uv run python manage.py spectacular \
  --file schema.yaml --validate --fail-on-warn \
  --settings core.settings.ci

CI fails if the committed schema differs from the generated one.

Management commands

Run uv run python manage.py help to list every available command. In addition to Django's built-ins, the project ships these commands:

Command App Purpose
sync_bss_users common Synchronise users and groups from BSS Login (Authentik).
google_calendar common Synchronise requests with Google Calendar.
update_request_status video_requests Recalculate the status of video requests.
email_daily_reminders video_requests Send daily reminder e-mails.
email_overdue_requests video_requests Send the weekly overdue request digests.
email_stale_todos video_requests Send the weekly digests of todos open for four weeks.
email_unfinished_requests video_requests Notify about unfinished requests.
email_weekly_tasks video_requests Send the weekly task summary e-mail.
seed_dev_data devtools Replace the requests and test users with test data.
dev_session devtools Print a browser login for any user.

The e-mail and status commands are normally invoked on a schedule by Celery beat; run them manually for testing or one-off operations. The devtools commands exist only with the debug and test settings (see Test data).

Runbooks

Merging duplicate users

When a person ends up with two accounts, reassign their related objects to the account you want to keep, then delete the duplicate.

The Django admin delete page lists every related object for a user; if there are only a few, edit them by hand. For bulk reassignment use the shell — example moving the Video objects edited by user 123 to user 234:

uv run python manage.py shell
from django.contrib.auth.models import User
from video_requests.models import Video

old = User.objects.get(pk=123)
new = User.objects.get(pk=234)

videos = Video.objects.filter(editor=old)
print(videos)              # review before changing
videos.update(editor=new)

Repeat for every relation that points at the old user, then delete it from the admin panel.