Reusable Django 6 starter for small-business API apps. Clone, rename, build.
- Python 3.13, uv for dependency management
- Django 6, Postgres via
psycopg django-allauth(email login + Google OAuth)django-anymailwith Postmark for transactional emailsentry-sdkfor production error reportingargon2-cffias the default password hasher- Custom
backofficeapp gated to staff users via middleware (replaces Django admin entirely)
No static-file serving baked in (no WhiteNoise). The deployment layer (reverse proxy / CDN) handles /static/ if needed.
uv sync
cp .env.example .env
# generate SECRET_KEY
python -c "import secrets; print(secrets.token_urlsafe(50))"
# paste into .env, then:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverSettings are split across config/settings/{base,dev,prod}.py. The module loaded is chosen by DJANGO_ENV in .env:
dev— default;DEBUG=True, console email, debug toolbar, django-extensions.prod—DEBUG=False, security headers, HSTS, Postmark email, Sentry ifSENTRY_DSNis set.
Per-developer overrides go in .env, not in a settings file.
Apps live under apps/ and import via dotted paths (apps.users, apps.backoffice):
mkdir apps/<name>
python manage.py startapp <name> apps/<name>Then in the generated apps/<name>/apps.py, change name = "<name>" to name = "apps.<name>" and add label = "<name>". Finally add "apps.<name>" to LOCAL_APPS in config/settings/base.py.
The backoffice app is mounted at /backoffice/ and gated by StaffOnlyBackofficeMiddleware:
- Anonymous users hitting
/backoffice/*are redirected to login. - Authenticated users without
is_staff=Truereceive 403. - The middleware uses Django's URL resolver to detect backoffice routes via
app_name = "backoffice", so you can change where it's mounted inconfig/urls.pywithout touching middleware.
To swap the check from is_staff to is_superuser (stricter), edit apps/backoffice/middleware.py. Add views/URLs as you normally would — no per-view auth decorators needed.
Django's built-in admin (django.contrib.admin) is not installed. If you ever need it back, re-add "django.contrib.admin" to DJANGO_APPS in base.py and mount path("admin/", admin.site.urls) in config/urls.py.
Set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET in .env. allauth reads them from SOCIALACCOUNT_PROVIDERS["google"]["APP"] in base.py, so no SocialApp admin entry is required.
- Create a Postmark server token.
- Set
POSTMARK_SERVER_TOKEN,DEFAULT_FROM_EMAIL,SERVER_EMAILin.env. - In
devthe console backend is used (emails print to terminal); inprod,anymail.backends.postmark.EmailBackendsends through Postmark.
Set SENTRY_DSN in production .env. Leave empty to disable. SENTRY_ENVIRONMENT defaults to production.
Optional. Set REDIS_URL=redis://... to use Redis; otherwise local-memory cache is used (single-process; fine for dev / small deployments).
uv run ruff check
uv run ruff format