JobApply is a Django-based job application tracker built around a Google-first workflow:
- Google OAuth is the only authentication method (no passwords).
- Google Drive backups provide automated, rotation-based cloud backups.
- Gmail Statistics (read-only) enables automated sync and classification of job-related email responses.
- Google Calendar integration #TODO .
The project is designed for dev-friendly, one-command startup via Docker Compose.
If you already live in Google Workspace, you don’t want another app with yet another password and a fragile export flow. JobApply uses Google as the identity provider and (optionally) Google Drive as the storage layer for backups.
-
Google-only sign-in (django-allauth)
-
Printable & PDF-Ready Applications Dashboard with Filters and Sorting
-
The application integrates with Gmail to automatically sync, classify, and calculate job-related responses such as rejections, interview invitations, and acknowledgements for analytics.
-
Optional Google Drive connection
- Create
JobApply/folder (and optionalbackups/subfolder) - Upload backups (CSV/XLSX)
- List & download backup files
- Disconnect Drive (revoke local tokens / unlink)
- OPTIONAL Auto Backup to Google Drive (latest + 2 retention)
- Create
-
Applications CRUD with statuses + filters
-
Interview planner (linked to applications)
-
Services: local import/export + statistics
-
Terms/consent gate for data processing (first-time user flow)
-
Cloudflare Turnstile
- Anti-bot gate before Google OAuth
- Applied to anonymous users only (never shown to authenticated users)
- Python 3.14 (container image:
python:3.14-rc-slim) - Django 5+
- PostgreSQL 18
- Docker Compose v2 (
docker compose ...) - Poetry for dependency management (installed in container)
- Pytest
- Google integrations:
- django-allauth for OAuth authentication
- Google Drive API via
google-api-python-client - Gmail API via
google-api-python-client(read-only analytics & sync) - Cloudflare Turnstile for pre-authentication bot protection
- Docker + Docker Compose v2 installed
- A Google Cloud project with OAuth credentials (see below)
Create a .env file next to docker-compose.yml (you can start from .env.example):
DJANGO_SECRET_KEY=change-me
DJANGO_DEBUG=1
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
POSTGRES_DB=jobapply
POSTGRES_USER=jobapply
POSTGRES_PASSWORD=jobapply
POSTGRES_HOST=db
POSTGRES_PORT=5432
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@example.com
DJANGO_SUPERUSER_PASSWORD=admin12345
# Google OAuth (django-allauth)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
DJANGO_SITE_DOMAIN=0.0.0.0:8000
DJANGO_SITE_NAME=JobApply
# Cloudflare Turnstile (anti-bot gate before Google OAuth)
TURNSTILE_ENABLED=1
TURNSTILE_SITE_KEY=...
TURNSTILE_SECRET_KEY=...
# Hide admin behind a custom URL (optional)
ADMIN_URL=admindocker compose up --buildThe web app will be available at:
Important: the container entrypoint is dev-friendly and does the plumbing for you:
- waits for DB
- runs migrations (when
DJANGO_AUTOMIGRATE=1) - creates/updates Google SocialApp from env (idempotent)
- creates superuser from env (idempotent)
- starts Django dev server
The container uses a dev-friendly entrypoint (entrypoint.sh) to make local setup painless. fileciteturn1file0
What it does, in order:
- Installs dependencies inside the container:
poetry install --no-interaction --no-ansi
- Waits for PostgreSQL to accept connections (up to ~60 seconds):
- Uses
psycopgto open/close a connection usingPOSTGRES_*env vars.
- Uses
- (Optional) Auto-makemigrations for development
- Only if
DJANGO_AUTOMIGRATE=1 - Runs
python manage.py makemigrations --noinput
- Only if
- Runs migrations
python manage.py migrate --noinput
- Creates Google SocialApp from env (idempotent)
python manage.py create_google_socialapp_if_not_exists- This wires up
GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETautomatically.
- Creates a Django superuser from env (idempotent)
python manage.py create_superuser_if_not_exists
- Starts Django dev server
python manage.py runserver 0.0.0.0:8000
DJANGO_AUTOMIGRATE=1— runsmakemigrationson startup (DEV only; don’t use in prod)POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD— DB connectionGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET— used by the SocialApp creation commandDJANGO_SUPERUSER_USERNAME,DJANGO_SUPERUSER_EMAIL,DJANGO_SUPERUSER_PASSWORD— superuser creation
Why this matters: it turns “clone → docker compose up” into a predictable, repeatable workflow (no manual migrations / admin creation / social app setup).
In Google Cloud Console:
- Create / select a project
- Configure OAuth consent screen
- Create OAuth client ID (Web application)
- Add Authorized redirect URI:
http://localhost:8000/accounts/google/login/callback/
If you run behind a custom domain later, add its callback URL too.
Set:
GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRET
This project is intentionally Google-first:
-
/- authenticated users are redirected directly to the application
- anonymous users are redirected to the Turnstile gate
-
/accounts/google/login/
Turnstile gate for anonymous users only (runs once per session,never shown to authenticated users) -
/accounts/google/oauth/
Starts Google OAuth flow after successful Turnstile verification -
/accounts/login/
Always redirected to the same Turnstile gate (Google-only authentication)
In Google Cloud Console:
- Your Project → APIs & Services → Library → Google Drive API → Enable
- Your Project → APIs & Services → Library → Gmail API → Enable
If Drive API is not enabled, you can still sign in, but Drive operations will fail.
Drive access is opt-in at the UI level:
- User logs in with Google
- User clicks Connect Google Drive
- App runs allauth connect flow (
process=connect) and stores tokens - Backups become available under Reports → Cloud backups
The app uses the drive.file scope:
https://www.googleapis.com/auth/drive.file
This is the minimal scope required for app-managed uploads in the user’s Drive.
JobApply can run automatic backups to Google Drive on a schedule.
- Runs every 15 minutes (background worker)
- Stores backups in your Drive under
JobApply/backups/ - Retention policy: keeps only 3 files:
latest.xlsx(most recent)backup-1.xlsxbackup-2.xlsx
- Rotation logic on each run:
backup-2is removedbackup-1 → backup-2latest → backup-1- a new backup is uploaded as
latest
- Per-user isolation: each user can enable/disable auto backup independently
- Requires Google Drive connection with offline access (
refresh_token) and the Drive API enabled in Google Cloud Console
The feature is optional and controlled via the Cloud Backups toggle in the UI.
JobApply can automatically analyze your Gmail inbox to calculate job-related responses.
- Manual or on-demand sync via UI or management command
- Fetches emails using the Gmail API (read-only)
- Stores message metadata locally for fast analytics
- Automatically classifies emails into:
RejectionInterview invitationAuto-acknowledgementGeneral responseNoise
- Provides aggregated statistics by selected time period
- Per-user isolation: each user syncs and analyzes only their own mailbox
- Requires Google connection with gmail.readonly scope and the Gmail API enabled in Google Cloud Console
The feature does not send, modify, or delete emails — all operations are strictly read-only.
Admin is optionally exposed under a custom path via ADMIN_URL.
Example:
If ADMIN_URL=admin, the admin panel is available at
http://localhost:8000/admin/.
If ADMIN_URL is not set, the admin route is not registered.
Superuser credentials are configured via .env:
DJANGO_SUPERUSER_USERNAMEDJANGO_SUPERUSER_PASSWORD
docker compose exec web bashdocker compose exec web poetry run python manage.py <command>docker compose down --remove-orphans -vUpload fixtures into the DB and assign them to your Google user:
docker compose exec web python manage.py loaddata fixtures/applications.json && docker compose exec web python manage.py assign_fixtures_owner --email you-google-email@gmail.com --from-user-id 1Dry-run verification:
docker compose exec web python manage.py assign_fixtures_owner --email you-google-email@gmail.com --from-user-id 1 --dry-run- Run all pytest-tests
docker compose exec web poetry run pytest -ra -vv-
Google Calendar integration (create interview events, reminders, sync)
-
Django Paginator(fix list_applications qs = qs.order_by(sort)[:200])
-
Email notifications
-
Tech support form
-
Mobile version
-
Testing(pytest)
-
Stronger backup/restore workflows (one-click restore)
Author: Maksym Petrykin
Email: m.petrykin@gmx.de
Telegram: @max_p95