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 are the flagship integration (CSV/XLSX export + restore-ready storage).
- Google Calendar integration is planned next (roadmap).
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)
-
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)
- 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 (OAuth)
- Google Drive API via
google-api-python-client
- 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=JobApplydocker 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:
/redirects to Google login (oauth2_login)/accounts/login/is also forced to Google login
In Google Cloud Console:
- APIs & Services → Library → Google Drive 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.
Django admin:
Credentials are created from .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