Skip to content

petar0/ApplyTrack

Repository files navigation

ApplyTrack

A job & internship application tracker for students and junior developers.

ApplyTrack replaces the scattered spreadsheet-plus-notes-app approach to job hunting with one organized, private place to track every application — its status, deadline, and outcome — from "Wishlist" all the way to "Accepted."

Built with Django, PostgreSQL, and Bootstrap 5, structured the way a small production SaaS app is structured: isolated apps by responsibility, a custom user model, query-level multi-tenancy, environment-based configuration, and a real test suite.

Python Django PostgreSQL Bootstrap License


Table of Contents


Features

Accounts

  • Registration, login, logout with a custom User model (extends AbstractUser)
  • Editable profile: avatar, university/bootcamp, bio, GitHub/LinkedIn/portfolio links
  • In-app password change

Application tracking

  • Full CRUD on applications, scoped so a user can only ever see or touch their own data
  • 7-stage status pipeline: Wishlist → Applied → Interview → Technical Interview → Offer → Rejected / Accepted
  • Company, position, location, type (Internship / Junior / Remote), date applied, deadline, job posting link, salary (optional), notes
  • Search by company/position/location, filter by status and type, sort by date/company/deadline/status, pagination
  • Business-rule validation (e.g. an "Applied" status requires a date applied; a deadline can't precede it)

Dashboard

  • Total applications, interview count, offer count, acceptance rate, rejection rate, applications this month
  • Status breakdown chart and a 6-month application trend chart
  • Upcoming deadlines (next 14 days) and a recent-activity feed

Engineering

  • Environment-driven settings (django-environ) — no secrets in source control
  • Custom 404 / 500 error pages
  • WhiteNoise for static files, Docker + docker-compose for one-command local setup
  • GitHub Actions CI (checks, migration drift detection, test suite)
  • A real test suite covering models, forms, and — most importantly — cross-user data isolation

Tech Stack

Layer Choice
Backend Django 5.2 (LTS)
Database PostgreSQL 16
DB adapter psycopg 3
Frontend Django Templates, Bootstrap 5, vanilla JS
Forms django-crispy-forms + crispy-bootstrap5
Charts Chart.js
Config django-environ (12-factor .env)
Static files WhiteNoise
Prod server Gunicorn
Containerization Docker / docker-compose

Architecture

The project is split into three focused Django apps instead of one monolithic app — each has a single, clear responsibility, which keeps models.py/views.py short and makes the codebase easy to navigate for anyone joining the project cold:

accounts/       → Custom User model, auth views, profile
applications/   → The core domain model (Application) + CRUD + list filtering
dashboard/      → Read-only aggregation views over applications (no models of its own)
config/         → Project-wide settings, URLs, WSGI/ASGI, error handlers

A few decisions worth calling out:

  • Custom user model from commit #1. Swapping AUTH_USER_MODEL after a project has real data is a painful migration. Since this project extends AbstractUser from the start, adding fields like university or avatar later is a normal migration, not a rewrite.
  • Multi-tenancy enforced at the query layer, not just the UI. Every view that touches Application filters through Application.objects.for_user(request.user). Trying to view, edit, or delete another user's application by guessing a URL returns a 404, not a permission error that leaks the object's existence. This is covered explicitly in applications/tests.py.
  • status is a single current-state field, not a history log. That keeps the schema simple for v1 — see Roadmap for the natural extension.
  • Server-rendered, not an SPA. Search/filter/sort live in the URL's query string (bookmarkable, shareable, works with JS disabled). For a CRUD-heavy internal tool like this, a well-built multi-page app is a legitimate architecture choice, not a legacy one.

Data Model

User (accounts)
 ├─ id, username, email (unique), password
 ├─ first_name, last_name
 ├─ avatar, university, bio
 ├─ github_url, linkedin_url, portfolio_url
 └─ date_joined, created_at

Application (applications)
 ├─ id
 ├─ user_id ───────────────────► User (FK, CASCADE)
 ├─ company_name, position, location
 ├─ job_type      [internship | junior | remote]
 ├─ status        [wishlist | applied | interview | technical_interview
 │                 | offer | rejected | accepted]
 ├─ date_applied, deadline
 ├─ job_link, salary, notes
 └─ created_at, updated_at

Indexes: (user_id, status), (user_id, -created_at)

dashboard intentionally owns no models — it's a read-only aggregation layer over Application, which keeps stat calculations in one place instead of scattering Application.objects.filter(...) logic across templates.


Getting Started

Option A — Docker (recommended)

This starts PostgreSQL and the app together, runs migrations automatically, and needs nothing installed locally except Docker.

git clone https://github.com/<your-username>/ApplyTrack.git
cd ApplyTrack
cp .env.example .env
docker compose up --build

Visit http://localhost:8000. To load realistic sample data:

docker compose exec web python manage.py seed_demo_data
docker compose exec web python manage.py createsuperuser

Option B — Local virtualenv

Prerequisites: Python 3.12+, PostgreSQL 16 running locally.

git clone https://github.com/<your-username>/ApplyTrack.git
cd ApplyTrack

python -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate

pip install -r requirements.txt

# Create the database (adjust user/password to taste)
createdb applytrack

cp .env.example .env              # then edit .env — see below
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Visit http://localhost:8000.


Environment Variables

All configuration lives in .env (copy .env.example to start). Nothing environment-specific is hardcoded in settings.py.

Variable Description Example
SECRET_KEY Django's cryptographic signing key. Generate a real one for anything beyond local dev. django-insecure-...
DEBUG True locally, always False in production. True
ALLOWED_HOSTS Comma-separated hostnames Django will serve. localhost,127.0.0.1
DATABASE_URL Full PostgreSQL connection string. postgres://applytrack:applytrack@localhost:5432/applytrack
CSRF_TRUSTED_ORIGINS Comma-separated origins allowed to POST cross-site (needed behind HTTPS in production). https://applytrack.example.com

Generate a real SECRET_KEY with:

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Demo Data

To see the dashboard and list populated with realistic data instead of an empty state:

python manage.py seed_demo_data

This creates (or reuses) a demo / demopass123 account with ~14 sample applications spread across every status, company, and deadline scenario — useful for screenshots, demos, or just poking around the UI immediately after cloning.


Running Tests

python manage.py test

The suite focuses on the things that actually matter in a multi-tenant app: model behavior, form validation rules, and — most heavily — that no user can ever view, edit, or delete another user's applications. CI runs this on every push via .github/workflows/ci.yml, alongside manage.py check and a migration-drift check (makemigrations --check).


Project Structure

ApplyTrack/
├── accounts/                  # Custom User model, auth & profile views
│   ├── migrations/
│   ├── admin.py
│   ├── forms.py
│   ├── models.py
│   ├── urls.py
│   └── views.py
├── applications/               # Core domain: Application model, CRUD, filtering
│   ├── management/commands/    # seed_demo_data
│   ├── migrations/
│   ├── templatetags/
│   ├── admin.py
│   ├── forms.py
│   ├── models.py
│   ├── urls.py
│   └── views.py
├── dashboard/                  # Stats aggregation (no models of its own)
│   ├── urls.py
│   ├── utils.py
│   └── views.py
├── config/                     # Project settings, URLs, WSGI/ASGI, error handlers
│   ├── settings.py
│   ├── urls.py
│   └── views.py
├── templates/                  # base.html + app_base.html shells, per-app templates
│   ├── partials/
│   ├── accounts/
│   ├── applications/
│   ├── dashboard/
│   └── errors/                 # custom 404 / 500
├── static/
│   ├── css/style.css
│   ├── js/main.js
│   └── img/
├── media/                      # user-uploaded avatars (git-ignored)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── .env.example
└── manage.py

Deployment Notes

The app is deployable as-is to any platform that runs a Docker container or a standard Python/WSGI app (Render, Railway, Fly.io, a plain VPS with Gunicorn behind Nginx, etc.):

  1. Set DEBUG=False and a real SECRET_KEY in the environment.
  2. Point DATABASE_URL at a managed PostgreSQL instance.
  3. Set ALLOWED_HOSTS (and CSRF_TRUSTED_ORIGINS if behind a custom domain over HTTPS).
  4. Run python manage.py migrate and python manage.py collectstatic --noinput as part of your deploy step (the Dockerfile already does the latter at build time).
  5. Serve with gunicorn config.wsgi:application. WhiteNoise handles static files directly from the app process, so no separate static file server is required for a small-to-medium deployment.

When DEBUG=False, SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, and CSRF_COOKIE_SECURE are all on by default (see config/settings.py) — disable them only if you have a specific reason (e.g. TLS terminated somewhere that doesn't set X-Forwarded-Proto).


Roadmap

Deliberately left out of v1 to keep the core model easy to reason about, but natural next steps:

  • ApplicationStatusChange history model — record every status transition with a timestamp, enabling real funnel analytics (e.g. "average days from Applied to Offer") instead of only current-state snapshots.
  • Email reminders for upcoming deadlines.
  • REST API (Django REST Framework) for a future mobile client or browser extension that captures job postings directly.
  • Tags/labels for finer-grained organization than the three built-in job types.
  • OAuth login (GitHub/Google) alongside username/password.

License

MIT — free to use, fork, and adapt.

About

ApplyTrack is an open-source, web-based internship and job application tracker designed to help students and junior developers organize, manage, and track their applications in one place.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors