Full-stack coding exercise system with instructor authoring, async learner submissions, per-test evaluation, and JWT auth.
- Instructor and learner roles with JWT authentication
- Exercise creation and editing with multi-language templates
- Visible and hidden test cases
- Asynchronous code execution via Celery + Redis — Run and Submit return immediately; results are polled by the frontend
- Submission history and per-test result breakdown
- Saved learner drafts in editor
- Solved problem indicator and "My Solution" panel
| Language | Template authoring | Code execution |
|---|---|---|
| JavaScript | Yes | Yes (node:20-alpine) |
| Python | Yes | Yes (python:3.12-slim) |
| C++ | Yes | Yes (gcc:14) |
| Java | Yes | Yes (eclipse-temurin:21-jdk-alpine) |
| Layer | Technology |
|---|---|
| Backend | Django 5.2 + Django REST Framework |
| Auth | SimpleJWT (JWT access + refresh tokens) |
| Task queue | Celery 5 |
| Message broker / result backend | Redis 7 |
| Code execution | Docker-out-of-Docker sandboxed containers |
| Frontend | React 19 + Vite |
| Database | SQLite (default) |
Each code submission spawns one Docker container per test case. Container startup, compilation (C++/Java), and execution can take several seconds. Running this synchronously would block the Django request thread and time out under any load.
Browser → POST /run or /submit
│
▼
Django view
(validates, creates DB record for submit)
│
▼ .delay()
Celery task ──► Redis (broker)
│
▼ (worker picks up task)
CodeRunner
(spawns Docker containers per test case)
│
▼
Results stored in Redis (run) or DB (submit)
│
▼ polling
Browser ◄── GET /tasks/{id}/ or GET /submissions/{id}/
POST /api/exercises/{id}/run/→ dispatchesevaluate_run_task→ returns{"task_id": "..."}(HTTP 202)- Frontend polls
GET /api/tasks/{task_id}/every 500 ms - When
state == "SUCCESS", the full result payload is returned
POST /api/exercises/{id}/submit/→ createsCodingSubmission(status="queued")→ dispatchesevaluate_submission_task→ returns the queued submission (HTTP 202)- Frontend polls
GET /api/submissions/{id}/every 500 ms - When
status != "queued", the completed submission (passed/failed/error) is returned
- Python 3.12+
- Node.js 20+
- Docker Desktop (running)
- Redis 7 — install via one of:
- Windows (WSL2):
sudo apt install redis-server && redis-server - macOS:
brew install redis && brew services start redis - Docker:
docker run -d -p 6379:6379 redis:7-alpine
- Windows (WSL2):
# Copy the example env file
cp .env.example .envThe defaults in .env.example work for local development without changes.
# Create and activate virtual environment
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
# Install dependencies (includes celery[redis])
pip install -r requirements.txt
# Run database migrations
python backend/manage.py migrate
# (Optional) Seed demo users
python backend/manage.py seed_demo
# Creates: instructor / pass123 and learner / pass123
# Start Django dev server
python backend/manage.py runserver
# → http://127.0.0.1:8000The Celery worker processes code execution tasks. It must be running for Run and Submit to complete.
venv\Scripts\activate
cd backend
celery -A config worker --loglevel=infoYou should see output like:
[tasks]
. coding.tasks.evaluate_run_task
. coding.tasks.evaluate_submission_task
[2024-...] celery@hostname ready.
cd frontend
npm install
npm run dev
# → http://127.0.0.1:5173| Process | Command | Port |
|---|---|---|
| Redis | redis-server (or Docker) |
6379 |
| Django API | python backend/manage.py runserver |
8000 |
| Celery worker | celery -A config worker --loglevel=info (from backend/) |
— |
| React dev server | npm run dev (from frontend/) |
5173 |
Docker Compose starts Redis, Django, the Celery worker, and pre-pulls all language runtime images automatically.
docker-compose up --buildServices started:
| Service | Description |
|---|---|
redis |
Redis 7 broker + result backend |
web |
Django API on port 8000 |
celery |
Celery worker (concurrency 2), shares Docker socket |
pull-images |
One-shot init that pre-pulls all language Docker images |
Both web and celery mount /var/run/docker.sock so they can spawn code-runner containers on the host (Docker-out-of-Docker).
The Redis URL is injected automatically by docker-compose via environment overrides — no changes to .env needed.
venv\Scripts\activate
python backend/manage.py test codingTests run with Celery in eager mode (tasks execute synchronously in-process, no broker needed) and CodeRunner.run_code mocked, so no Redis or Docker is required to run the test suite.
POST /api/auth/register/ body: {username, password, role}
POST /api/auth/login/ body: {username, password}
POST /api/auth/refresh/ body: {refresh}
GET POST /api/instructor/exercises/
GET PATCH DELETE /api/instructor/exercises/{id}/
GET POST /api/instructor/exercises/{id}/testcases/
GET PATCH DELETE /api/instructor/exercises/{id}/testcases/{tc_id}/
GET /api/exercises/
GET /api/exercises/{id}/
POST /api/exercises/{id}/run/ → {task_id} (HTTP 202)
POST /api/exercises/{id}/submit/ → queued submission (HTTP 202)
GET /api/exercises/{id}/submissions/
GET /api/submissions/{id}/ poll until status != "queued"
GET /api/tasks/{task_id}/ poll until state == "SUCCESS"
backend/
config/
celery.py ← Celery app
settings.py ← Django + Celery/Redis config
urls.py
coding/
models.py
serializers.py
views.py ← run/submit dispatch tasks (HTTP 202)
tasks.py ← evaluate_submission_task, evaluate_run_task
permissions.py
urls.py
tests.py
services/
code_runner.py ← Docker sandbox runner
management/
commands/
seed_demo.py
frontend/
src/
api.js ← fetch wrapper + pollTask() + pollSubmission()
pages/
LearnerExercisePage.jsx ← async polling for run/submit
...
docker/
runners/ ← per-language stub Dockerfiles
docker-compose.yml
Dockerfile
requirements.txt
.env.example
Copy .env.example → .env before running locally.
| Variable | Default | Description |
|---|---|---|
DJANGO_SECRET_KEY |
(change this) | Django/JWT secret |
DJANGO_DEBUG |
True |
Debug mode |
DJANGO_ALLOWED_HOSTS |
localhost,127.0.0.1 |
Allowed hosts |
DJANGO_TIME_ZONE |
UTC |
Timezone |
DB_ENGINE |
django.db.backends.sqlite3 |
Database backend |
DB_NAME |
backend/db.sqlite3 |
Database path |
JWT_ACCESS_MINUTES |
30 |
Access token lifetime |
JWT_REFRESH_DAYS |
7 |
Refresh token lifetime |
CELERY_BROKER_URL |
redis://localhost:6379/0 |
Redis broker |
CELERY_RESULT_BACKEND |
redis://localhost:6379/0 |
Redis result store |
.envis git-ignored — never commit real secrets.env.exampleis committed as a safe template- Code runner containers have no network, read-only FS, 128 MB RAM cap, 0.5 CPU cap, and all Linux capabilities dropped
- The Docker-out-of-Docker setup is demo-only — for production use a purpose-built sandbox service (e.g. Firecracker, gVisor, or a managed code execution API)