A Django-based web application for managing and participating in DataTalks.Club courses.
The platform supports course administration, homework and project submissions, peer review workflows, and course leaderboards. It also provides API access to course data.
The main features are:
- User authentication: registration and login.
- Course management: instructors can create and manage courses.
- Homework and projects: students can submit homework and projects.
- Peer reviews: students can evaluate project submissions from their peers.
- Leaderboard: course rankings based on submitted work and scores.
- API access: authenticated endpoints for course, homework, project, graduate, certificate, and OpenAPI data.
- Health check: a public endpoint for service monitoring.
The main directories are:
├── accounts/ # User accounts and authentication
├── api/ # OpenAPI schema and API tests
├── cadmin/ # Custom admin views
├── course_management/ # Django project settings and root configuration
├── courses/ # Course, homework, project, and review logic
├── data/ # Public and authenticated data API views
├── deploy/ # Deployment scripts
├── docs/ # Project documentation
├── notebooks/ # Analysis notebooks
├── templates/ # Shared Django templates
Install these tools before running the app:
- Python 3.13
- uv
- Docker and Docker Compose, for containerized local development
Install uv with the official installer:
curl -LsSf https://astral.sh/uv/install.sh | shInstall Python 3.13 and project dependencies:
uv python install 3.13 uv sync --dev
Run the full pytest suite and Django checks against PostgreSQL 16, as in CI:
make tests-postgres
This needs Docker and uv. It starts a disposable database on an available
loopback port and removes the container when tests finish. It does not use
your configured application database. To pass pytest options, use
bash scripts/test_postgres.sh -q.
Use SQLite for local development:
export DATABASE_URL="sqlite:///db/db.sqlite3"
Prepare the database:
make migrations
Create an admin user:
make admin
Load sample data:
make data
Run the development server:
make run
The app will be available at:
http://localhost:8000
Useful direct Django commands:
uv run python manage.py makemigrations uv run python manage.py migrate uv run python manage.py createsuperuser uv run python manage.py runserver 0.0.0.0:8000
Run the Django test suite:
make tests
Direct command:
uv run python manage.py test --timing --durations 30One-time scripts and temporary debug files should go in .tmp/, which is
ignored by git.
Start the app and PostgreSQL with Docker Compose:
docker-compose up --build
Compose starts:
web: the Django application on port 8000db: PostgreSQL 17ngrok: opt-in TCP tunnel for the database; start it withdocker compose --profile db-tunnel up ngrok
The app will be available at:
http://localhost:8000
Build and run the application image without Compose:
docker build -t course_management .Then run the image:
DBDIR=`cygpath -w ${PWD}/db` docker run -it --rm \ -p 8000:80 \ --name course_management \ -e DATABASE_URL="sqlite:////data/db.sqlite3" \ -e SITE_ID="${SITE_ID}" \ -v ${DBDIR}:/data \ course_management
Open a shell in the running container:
docker exec -it course_management bashThe public health check endpoint returns service status and version:
GET /api/health/
Example:
curl http://localhost:8000/api/health/
Response:
{
"status": "ok",
"version": "0.1.0"
}In local development, the version comes from the VERSION environment
variable and falls back to local-development-build-version-not-configured.
The platform syncs users and course enrollments and sends email through Relay.
Set all required environment variables to enable the integration:
export RELAY_URL="https://relay.datatalks.club" export RELAY_API_KEY="<token>" export RELAY_CLIENT="dtc-courses" export RELAY_AUDIENCE="dtc-courses" export RELAY_FROM_EMAIL="courses"
Optional settings:
export RELAY_STRICT="0" export RELAY_SYNC_ON_USER_CREATE="1"
With RELAY_STRICT=0, Relay API failures are logged and don't
break signup or enrollment flows. Set RELAY_STRICT=1 only when those
flows should fail on Relay errors.
PUBLIC_BASE_URL is used for links in delivered transactional email. Set it
when sending email from a local server so messages contain public HTTPS links
instead of localhost links.
export PUBLIC_BASE_URL="https://dev.courses.datatalks.club"
To exercise the transactional email path without delivering anything, set
RELAY_TRANSACTIONAL_DRY_RUN=1. CMP then adds Relay's dry_run flag
to every POST /api/transactional/send: the identical prod pipeline runs
(outbox -> dispatch -> send -> DatamailerSendAudit), but Relay renders
the email and returns it inline without sending, queuing, or persisting
anything. DatamailerSendAudit is retained as a historical database model name;
it does not select or configure the old service.
The rendered subject/bodies land in the audit's response_payload["rendered"],
which CMP exposes over HTTP at GET /api/datamailer/send-audits
(filter by email / template_key / idempotency_key). The e2e smoke suite
uses this to verify confirmation emails safely.
Deadline reminder emails are triggered by a short CMP management command:
uv run python manage.py send_deadline_reminders
In production, Coolify runs the recurring reminders task against the stable private release service. The schedule and worker ownership are documented in CMP-WORKERS.md. The former ECS deployment path is summarized in legacy-aws-deploy.md.
The scheduled task exits after reconciling Relay recipient lists and triggering Relay list sends. Relay handles per-recipient delivery asynchronously.
Every event is attempted even if an earlier one fails; each failure is written
to stderr and recorded on the DatamailerSendAudit row, and the command exits
non-zero at the end if any event failed. To see why a reminder never arrived:
curl -s -H "Authorization: Token $TOKEN" \ "https://courses.datatalks.club/api/datamailer/send-audits?template_key=deadline-reminder"
Bulk sends post the whole recipient list inline, so they use a longer HTTP
timeout than transactional sends (RELAY_TIMEOUT_SECONDS, default 60).
Don't lower it: hanging up mid-request makes Relay abandon the dispatch it
is partway through, stranding every not-yet-sent message at queued with no
error and no retry, while CMP records a failure for work that partly succeeded.
Relay transactional template keys are stable code-level constants in CMP. Don't configure one environment variable per template.
The CMP/Relay integration, including the conceptual design and API reference,
is documented in the historically named
docs/datamailer-integration.md.
Most /api endpoints require token authentication:
Authorization: Token <token>
Example:
TOKEN="TEST_TOKEN" HOST="http://localhost:8000" COURSE="fake-course" HOMEWORK="hw1" curl \ -H "Authorization: Token ${TOKEN}" \ "${HOST}/api/courses/${COURSE}/homeworks/${HOMEWORK}/submissions"
Run make data to create sample data, including an authentication token.
See endpoints.md for API documentation, including:
- OpenAPI specification
- Public course criteria
- Public health check
- Public leaderboard data
- Homework data
- Project data
- Graduate data
- Certificate updates
- Course management API
OAuth is optional for local testing.
To configure Google OAuth locally:
- Go to the admin panel at
http://localhost:8000/admin. - Add a record to
Sites. - Use
localhost:8000for the display name and domain name. - Note the site ID, usually
2. - Add a record to
Social applications. - Use
GoogleDTCas the name andGoogleas the provider. - Ask for the local OAuth keys. Don't share them publicly.
- Attach the application to the localhost site.
Export the local site ID:
export SITE_ID=2Restart the app:
uv run python manage.py runserver 0.0.0.0:8000
Then log out as admin and log in with Google.
The old AWS ECS deployment and RDS tunnel instructions are no longer usable. See legacy-aws-deploy.md for a short historical description of that path.