SMP is a Django web application to manage recurring subscriptions with user-scoped data, dashboards, lifecycle actions, and renewal tracking.
- Authentication: sign up, sign in, sign out.
- User-scoped CRUD for:
- Providers
- Billing cycles
- Subscriptions
- Notification rules
- Renewal events
- Subscription lifecycle actions:
- Pause
- Resume
- Cancel
- Subscription history timeline (created/updated/status changes).
- Dashboard with:
- Entity counts
- Monthly and annual totals (base currency)
- Upcoming renewals
- Subscription list filters (provider, status, cost range, ordering).
- Python + Django 5
- Templates + UIkit
- Gunicorn + WhiteNoise
- PostgreSQL (primary) via
DATABASE_URL - SQLite fallback for local/dev convenience
- Quality tooling: Ruff + mypy + pre-commit
- CI: GitHub Actions
.
├── asgi.py
├── manage.py
├── settings.py
├── urls.py
├── wsgi.py
├── Procfile
├── pyproject.toml
├── requirements.in
├── requirements.txt
├── templates/
├── subscriptions/
│ ├── models.py
│ ├── views.py
│ ├── forms.py
│ ├── services.py
│ ├── urls.py
│ └── tests/
│ ├── test_views.py
│ ├── test_services.py
│ └── test_models.py
└── .github/
├── dependabot.yml
└── workflows/ci.yml
- Create and activate a virtual environment.
- Install dependencies.
- Run migrations.
- Start the server.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserverInstall pip-tools once in your active virtualenv:
pip install pip-toolsrequirements.incontains direct dependencies.requirements.txtis the lock file consumed by local setup and CI.
Regenerate lock file without upgrading versions:
pip-compile --output-file=requirements.txt requirements.inUpgrade all dependencies and refresh lock file:
pip-compile --upgrade --output-file=requirements.txt requirements.inUpgrade a single dependency and refresh lock file:
pip-compile --upgrade-package Django --output-file=requirements.txt requirements.inDatabase configuration priority in settings.py:
DATABASE_URLKOYEB_DATABASE_URL- Built URL from split variables:
KOYEB_DB_NAME,KOYEB_DB_USER,KOYEB_DB_PASSWORD,KOYEB_DB_HOST- or
POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_HOST
- SQLite fallback (
db.sqlite3)
Useful variables:
DATABASE_URLKOYEB_DATABASE_URLKOYEB_DB_NAMEKOYEB_DB_USERKOYEB_DB_PASSWORDKOYEB_DB_HOSTPOSTGRES_DBPOSTGRES_USERPOSTGRES_PASSWORDPOSTGRES_HOSTDJANGO_ALLOWED_HOSTS
Example:
export DATABASE_URL='postgresql://user:password@host/dbname?sslmode=require'ruff check .mypy --config-file pyproject.tomlpython manage.py makemigrations --check --dry-runpython manage.py testInstall git hooks:
pre-commit installRun all hooks manually:
pre-commit run --all-filesConfigured hooks include:
- Basic file hygiene hooks
- Ruff
- mypy
- Django migration check (
makemigrations --check --dry-run)
/.github/workflows/ci.yml runs:
- Dependency install
- Ruff lint
- mypy type check
- Migration drift check
- Migrations
- Django startup smoke checks
- Test suite
Procfile runs:
python manage.py migrate --noinputpython manage.py collectstatic --noinputgunicorn wsgi:application ...
For production, define environment variables in your platform (for example Koyeb) instead of relying on local .env.