A production-ready Django REST Framework starter with an opinionated, domain-driven file architecture, JWT authentication (Djoser + SimpleJWT), Celery background jobs, Redis cache/broker, OpenAPI docs (drf-spectacular), CORS, and a modern Unfold-powered admin.
- Authentication: Djoser + SimpleJWT (access/refresh), custom
accounts.User - API: DRF with sensible defaults, filtering (
django-filter), extensions - Docs: OpenAPI schema + Swagger/Redoc UIs
- Background jobs: Celery worker + beat
- Realtime: Django Channels + Redis (WebSockets)
- Storage: Postgres via
dj-database-url(SQLite supported viaDATABASE_URL) - Cache/Broker: Redis
- Admin: Unfold (modern UI) + structured logging via Loguru
- Dev UX: uv for env/deps,
django-extensions,silk,rosetta, pytest stack
- Python: 3.12+
- uv: package/dependency manager and venv tool by Astral
- Install on Linux/macOS:
curl -LsSf https://astral.sh/uv/install.sh | sh - On Windows (PowerShell):
iwr https://astral.sh/uv/install.ps1 -useb | iex
- See:
https://astral.sh/uv
- Install on Linux/macOS:
- For full stack locally (optional if you use SQLite):
- PostgreSQL 15+
- Redis 7+
- Or use Docker and docker compose (ships with
Dockerfileanddocker-compose.yml).
Because this project is fully dockerized, run Django and management commands inside containers. Running them directly on your host will fail unless you reconfigure .env (see Host mode below).
- You can set up the starter in one command:
curl -LsSf https://raw.githubusercontent.com/Katestheimeno/Katesthe-core/main/setup.sh | sh- Create environment configuration
# For local development
cp .env.local.example .env.local
# For production (optional)
cp .env.prod.example .env.prod
# For testing (optional)
cp .env.test.example .env.test- Set secrets for your environment
# Generate strong values for production
openssl rand -base64 48 | tr -d '\n'- Start the stack
# Local development (uses .env.local by default)
docker compose up --build
# Or specify environment explicitly
ENV_FILE=.env.local docker compose up --buildDocker stack (services): db (PostgreSQL 15), redis, pgbouncer (connection pooler), web (Django + Daphne), celery_worker, celery_beat. The web and Celery services connect to Postgres via PgBouncer; PgBouncer connects to db. Use env.docker.example as the basis for .env.local (it points the app at pgbouncer:5432 inside the network). For PgBouncer config and optional host access, see config/pgbouncer/README.md.
The project uses environment-specific configuration files for better separation of concerns:
.env.local- Local development environment.env.prod- Production environment.env.test- Testing environment
The system automatically detects which environment file to use based on:
DJANGO_ENVenvironment variable (local,prod,test)DJANGO_SETTINGS_MODULEenvironment variable (containslocal,production, ortest)- Default to
.env.localif neither is set
Docker Compose uses the ENV_FILE environment variable to specify which .env file to load:
# Use local environment (default)
docker compose up
# Use production environment
ENV_FILE=.env.prod docker compose up
# Use test environment
ENV_FILE=.env.test docker compose upCopy the appropriate example file and customize:
# For local development
cp .env.local.example .env.local
# For production
cp .env.prod.example .env.prod
# For testing
cp .env.test.example .env.test-
Web container startup (
entrypoint.sh)When the
webservice starts, it runs in order:uv sync --frozenβ install/sync dependenciesmigrateβ apply database migrationscollectstatic --noinputβ gather static files intoSTATIC_ROOTfor serving- Daphne ASGI server on
${WEB_PORT:-8000}with--application-close-timeout 30(reduces "took too long to shut down" warnings when the browser cancels static requests, e.g. on refresh or redirect)
You do not need to run
collectstaticmanually for normal Docker runs. -
Run management commands inside the web container
# migrations (also run automatically at startup)
docker compose exec web uv run python manage.py migrate
# superuser
docker compose exec web uv run python manage.py createsuperuser
# makemigrations
docker compose exec web uv run python manage.py makemigrations
# shell
docker compose exec web uv run python manage.py shell- Access services
- Web:
http://127.0.0.1:${WEB_PORT:-8000} - Admin:
http://127.0.0.1:${WEB_PORT:-8000}/admin/ - Swagger:
http://127.0.0.1:${WEB_PORT:-8000}/api/schema/docs/ - Redoc:
http://127.0.0.1:${WEB_PORT:-8000}/api/schema/redoc/
If you prefer running Django on your host (outside Docker), you must point your environment file to services reachable from your host, not the Docker DNS names.
Two options:
-
Use the Dockerized Postgres/Redis but connect via localhost (ports are published by compose). To talk to Postgres directly (no PgBouncer): use port 5432; to use the pooler from the host: use port 6432 (see
config/pgbouncer/README.md).# .env.local overrides for HOST MODE (direct Postgres) DATABASE_URL=postgresql://postgres:postgres@localhost:5432/drf_starter REDIS_URL=redis://localhost:6379/0
Then:
# Ensure db and redis are up (pgbouncer optional if you use localhost:6432 from host) docker compose up -d db redis # Host virtualenv using uv uv venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate uv sync && uv sync --group dev uv run python manage.py migrate uv run python manage.py runserver 0.0.0.0:${WEB_PORT:-8000}
-
Or install Postgres/Redis locally and set
DATABASE_URL/REDIS_URLaccordingly.
Important:
- Do not use
db,pgbouncer, orredishostnames from your host; those names only resolve inside Docker networks. Uselocalhostwith the published ports (Postgres: 5432, PgBouncer: 6432, Redis: 6379).
The project centralizes env handling in config/env.py. Key variables:
SECRET_KEY: Django secret keyJWT_SECRET_KEY: JWT signing key (used by SimpleJWT)DJANGO_DEBUG:True|False(Django'sDEBUG)DJANGO_ENV:local|production(informational)ALLOWED_HOSTS: CSV list; required in productionWEB_PORT: Django development server port (default: 8000)
PROJECT_NAME: Project name used in API docs, admin interface, etc. (default: "Katesthe-core")PROJECT_DESCRIPTION: Project description for API documentation (default: "A Django REST Framework starter project...")PROJECT_VERSION: API version (default: "1.0.0")
CONTACT_NAME: Contact name for API documentation (default: "Katesthe-core Dev Team")CONTACT_EMAIL: Contact email for API documentation (default: "support@katesthe-core.com")CONTACT_URL: Contact URL/GitHub repository (default: "https://github.com/katesthe-core")
EMAIL_HOST: SMTP host (default: "localhost")EMAIL_PORT: SMTP port (default: 1025)EMAIL_USE_TLS: Use TLS for email (default: False)EMAIL_HOST_USER: SMTP username (default: "")EMAIL_HOST_PASSWORD: SMTP password (default: "")EMAIL_FRONTEND_DOMAIN: Frontend domain for email links (default: "")
THEME_PRIMARY_COLOR: Primary theme color in hex format (default: "#6a0dad")THEME_ACCENT_COLOR: Accent theme color in hex format (default: "#4b0082")
DATABASE_URL: e.g.postgresql://user:pass@host:5432/dbnameorsqlite:///database/db.sqlite3POSTGRES_*: used to buildDATABASE_URLif not set. In Docker, the app connects to Postgres via PgBouncer (POSTGRES_HOST=pgbouncer,POSTGRES_PORT=5432inside the network); seeenv.docker.exampleandconfig/pgbouncer/README.md.
REDIS_URL: e.g.redis://localhost:6379/0
Notes:
manage.pydefaults toconfig.django.localsettings; Production can useDJANGO_SETTINGS_MODULE=config.django.production.- Database configuration uses
dj-database-url. - Use
DJANGO_DEBUG(boolean) for Django's debug toggle. The example environment files includeDEBUGfor Compose convenience; preferDJANGO_DEBUG. - All branding and theme variables can be customized via environment variables for easy project personalization.
- Base API path:
api/v1/ - Auth routes: included from
accounts/urls/_auth.pyvia Djoser- JWT:
POST /api/v1/auth/jwt/create,POST /api/v1/auth/jwt/refresh,POST /api/v1/auth/jwt/verify - Users:
POST /api/v1/auth/users/(register),GET /api/v1/auth/users/me/(profile), etc.
- JWT:
- DRF defaults (
config/settings/restframework.py):IsAuthenticatedglobally; addAllowAnyper-view as needed- Auth classes: JWT + Session
- Filtering via
django-filter
- OpenAPI docs (
drf-spectacular):- Schema:
/api/schema/ - Swagger UI:
/api/schema/docs/ - Redoc:
/api/schema/redoc/
- Schema:
Example auth flow (host mode):
# Register
curl -X POST http://127.0.0.1:${WEB_PORT:-8000}/api/v1/auth/users/ \
-H 'Content-Type: application/json' \
-d '{"username":"demo","email":"demo@example.com","password":"Passw0rd!"}'
# Obtain JWT tokens
curl -X POST http://127.0.0.1:${WEB_PORT:-8000}/api/v1/auth/jwt/create \
-H 'Content-Type: application/json' \
-d '{"username":"demo","password":"Passw0rd!"}'
# Authorized request
curl http://127.0.0.1:${WEB_PORT:-8000}/api/v1/auth/users/me/ \
-H "Authorization: Bearer <ACCESS_TOKEN>"- App:
config/celery.py - Settings:
config/settings/celery.py(beat schedule, queues, routes placeholders)
With Docker Compose, celery_worker and celery_beat run automatically.
Useful commands:
# Tail logs
docker compose logs -f celery_worker
docker compose logs -f celery_beat
# (Advanced) run a one-off Celery command inside web
docker compose exec web uv run celery -A config.celery.app inspect activeThis starter uses an opinionated, domain-driven layout that encourages separation of concerns and scalability.
.
ββ config/
β ββ django/ # env-specific entry settings (local, production)
β ββ settings/ # modular settings split by concerns
β β ββ apps_middlewares.py # INSTALLED_APPS, middleware, dev apps
β β ββ auth.py # password validators, AUTH_USER_MODEL
β β ββ corsheaders.py # CORS config
β β ββ database.py # DATABASES, CACHES (Redis)
β β ββ lang_time.py # i18n/l10n, timezone
β β ββ paths.py # static/media paths, sqlite location
β β ββ restframework.py # DRF + SimpleJWT + Djoser config import
β β ββ spectacular.py # drf-spectacular settings
β β ββ unfold.py # Unfold admin UI config
β β ββ __init__.py # imports the modular settings
β ββ env.py # environment loader (django-environ)
β ββ celery.py # Celery app setup
β ββ pgbouncer/ # PgBouncer config (ini, userlist); see README there
β ββ urls.py # root URLs (admin, api, docs)
β ββ asgi.py / wsgi.py
β ββ logger.py # Loguru-based structured logging
β
ββ accounts/ # domain app (auth/user)
β ββ models/ # domain models (custom User)
β ββ serializers/ # DRF serializers
β ββ controllers/ # HTTP controllers (views)
β ββ services/ # write logic, side-effects, orchestration
β ββ selectors/ # read/query logic
β ββ handlers/ # integration or workflow handlers
β ββ permissions/ # DRF permissions
β ββ filters/ # DRF/django-filter integration
β ββ urls/ # app-level routes (Djoser mounted here)
β ββ admin/ # admin registrations (Unfold ModelAdmin)
β
ββ utils/
β ββ models/ # reusable base models: TimeStamped, UUID, SoftDelete, Trackable, Slugged
β ββ management/commands/ # developer tooling
β ββ starttemplateapp.py # scaffold a new app from a template
β ββ manageprojectapp.py # add/remove apps in settings lists
β ββ addfile.py # add actions/files to app sections
β
ββ static/exp_app/ # template app used by starttemplateapp
ββ database/ # sqlite db path (if used)
ββ Dockerfile # uv-based image
ββ entrypoint.sh # web startup: uv sync, migrate, collectstatic, daphne
ββ docker-compose.yml # db, redis, pgbouncer, web, celery_worker, celery_beat
ββ pyproject.toml # deps + dev deps + python version
ββ uv.lock # locked dependency versions
ββ manage.py # CLI entry
Why this layout?
- Explicit boundaries: reads (
selectors) vs writes (services) vs transport (controllers) - Composable apps: each app contains the same predictable sections
- Testability: thin views, test core logic in services/selectors
- Scaffolding: management commands accelerate adding apps/files consistently
All commands run inside containers by default:
-
Create a new app from the template and add it to settings:
docker compose exec web uv run python manage.py starttemplateapp blog --add-to-settings -
Add an action/file to a section (e.g., a controller):
docker compose exec web uv run python manage.py managefile blog --layer controllers --suffix list -
Add/remove an app from settings lists:
# add to project apps docker compose exec web uv run python manage.py manageprojectapp blog --type project # remove from third-party packages (soft-remove as a comment) docker compose exec web uv run python manage.py manageprojectapp somepackage --type third-party --remove --soft-remove
This project includes several custom management commands to streamline development and maintenance tasks. All commands can be run either locally (with proper environment setup) or inside Docker containers.
Purpose: Run Django management commands inside Docker containers from your local machine, ensuring consistent database connections and environment variables.
Use Cases:
- Running migrations when your local environment isn't configured for the Docker database
- Executing any Django command that requires database access
- Maintaining consistency between local development and containerized environments
- Interactive commands like shell, createsuperuser, and runserver
Features:
- Smart Interactive Mode: Automatically detects when commands need interactive mode (TTY allocation)
- Real-time Output Streaming: Non-interactive commands stream output as it happens
- Dry Run Support: Preview commands before execution
- Flexible Service Selection: Choose which Docker service to use
- Auto Docker Compose Discovery: Automatically finds docker-compose.yml files in multiple locations
- Custom Docker Compose Files: Specify different compose files with intelligent path resolution
- Keyboard Interrupt Handling: Graceful handling of Ctrl+C
Examples:
# Run database migrations
uv run python manage.py dockerexec migrate
# Make migrations for specific app
uv run python manage.py dockerexec makemigrations accounts
# Create a superuser (automatically interactive)
uv run python manage.py dockerexec createsuperuser
# Run Django shell (automatically interactive)
uv run python manage.py dockerexec shell
# Run tests
uv run python manage.py dockerexec test
# Dry run to see what would be executed
uv run python manage.py dockerexec migrate --dry-run
# Use different Docker service
uv run python manage.py dockerexec migrate --service=web-dev
# Force interactive mode for any command
uv run python manage.py dockerexec collectstatic --interactive
# Force non-interactive mode
uv run python manage.py dockerexec createsuperuser --no-interactive
# Use different Docker compose file (auto-detected)
uv run python manage.py dockerexec migrate --compose-file=docker-compose.prod.yml
# Use absolute path to compose file
uv run python manage.py dockerexec migrate --compose-file=/path/to/docker-compose.yml
# Run shell with specific settings
uv run python manage.py dockerexec shell --settings=config.django.testInteractive Commands (automatically get TTY allocation):
createsuperuser- User creation promptsshell- Interactive Python shelldbshell- Database shell accessrunserver- Development server
Non-Interactive Commands (stream output):
migrate,makemigrations- Database operationstest- Test executioncollectstatic- Static file collectioncheck- Django system checks
Docker Compose Auto-Discovery: The command automatically searches for docker-compose files in multiple locations:
- Absolute paths - Used as-is if the file exists
- Current directory - Relative to where you run the command
- Service directory - Where manage.py is located
- Project root - One level up from service directory
- Fallback - Looks for
docker-compose.ymlin project root
This means you can run the command from anywhere in your project structure and it will find the correct compose file automatically. If no compose file is found, the command will provide a clear error message showing all the locations it searched.
Configuration: Customize behavior by defining these settings in your Django settings:
# Commands that should use Docker
DOCKEREXEC_COMMANDS = [
'migrate', 'makemigrations', 'createsuperuser', 'shell',
'dbshell', 'collectstatic', 'runserver', 'test', 'flush',
'loaddata', 'dumpdata', 'showmigrations', 'sqlmigrate',
'check', 'compilemessages', 'makemessages'
]
# Commands that always need interactive mode
DOCKEREXEC_INTERACTIVE_COMMANDS = [
'createsuperuser', 'shell', 'dbshell', 'runserver'
]Purpose: Add suffix files to app layers with automatic import management and nested scope support. This command helps maintain the domain-driven architecture by creating properly structured files with correct imports.
Use Cases:
- Creating new controllers, serializers, models, or other layer files
- Organizing code into nested scopes (e.g.,
controllers/user/,serializers/auth/) - Managing imports automatically across the nested structure
- Enabling/disabling files by commenting/uncommenting imports
- Cleaning up empty directories and unused imports
Examples:
# Create a user controller
uv run python manage.py managefile accounts --layer controllers --suffix user
# Create a nested auth serializer
uv run python manage.py managefile accounts --layer serializers --suffix auth --scope user
# Create a review controller inside shop feature
uv run python manage.py managefile shops --layer controllers --suffix review --scope shop
# Disable an existing handler (comment out import)
uv run python manage.py managefile bookings --layer handlers --suffix cancel --disable
# Re-enable a disabled handler
uv run python manage.py managefile bookings --layer handlers --suffix cancel --enable
# Clean up empty directories in a layer
uv run python manage.py managefile accounts --layer controllers --cleanup
# Delete a specific file and clean up imports
uv run python manage.py managefile accounts --layer controllers --cleanup user
# Preview actions without making changes
uv run python manage.py managefile accounts --layer controllers --suffix user --dry-runFeatures:
- Automatic imports: Adds default imports based on layer type (controllers get DRF imports, models get Django imports, etc.)
- Nested scopes: Supports creating files in subdirectories with proper import chains
- Import management: Automatically updates
__init__.pyfiles throughout the nested structure - Enable/disable: Comment/uncomment imports to temporarily disable files
- Cleanup: Remove empty directories and unused imports
Purpose: Scaffold a new Django app from the template in static/exp_app with placeholder replacement and automatic settings integration.
Use Cases:
- Creating new domain apps following the project's architecture
- Ensuring consistent app structure across the project
- Automatically adding new apps to Django settings
- Customizing app templates for different use cases
Examples:
# Create a new blog app
uv run python manage.py starttemplateapp blog
# Create app and automatically add to PROJECT_APPS
uv run python manage.py starttemplateapp blog --add-to-settings
# Create app in a specific directory
uv run python manage.py starttemplateapp blog --dir apps
# Use a custom template
uv run python manage.py starttemplateapp blog --template custom_template
# Force overwrite if directory exists
uv run python manage.py starttemplateapp blog --force
# Preview actions without creating files
uv run python manage.py starttemplateapp blog --dry-runFeatures:
- Template-based: Uses
static/exp_appas the template - Placeholder replacement: Replaces
{{app_name}}placeholders throughout the template - Settings integration: Can automatically add the new app to
PROJECT_APPS - Directory flexibility: Create apps in custom directories
- Safety features: Dry-run mode and force overwrite options
Purpose: Add or remove apps from Django settings lists (PROJECT_APPS, DEV_APPS, THIRD_PARTY_PACKAGES) with intelligent handling of different app types.
Use Cases:
- Adding new project apps to settings
- Managing third-party package dependencies
- Soft-removing apps (commenting out instead of deleting)
- Organizing apps by type in settings files
- Maintaining clean settings files
Examples:
# Add a project app to PROJECT_APPS
uv run python manage.py manageprojectapp blog --type project
# Add a third-party package to THIRD_PARTY_PACKAGES
uv run python manage.py manageprojectapp rest_framework --type third-party
# Add a development tool to DEV_APPS
uv run python manage.py manageprojectapp django_extensions --type dev
# Soft-remove a project app (comment out instead of deleting)
uv run python manage.py manageprojectapp blog --remove --soft-remove
# Hard-remove a third-party package (delete the line)
uv run python manage.py manageprojectapp somepackage --type third-party --remove
# Force add an app without checking if its folder exists
uv run python manage.py manageprojectapp some_missing_app --force
# Preview changes without modifying files
uv run python manage.py manageprojectapp blog --type project --dry-runFeatures:
- App type handling: Different logic for project apps vs third-party packages
- Soft removal: Comment out apps instead of deleting them
- Validation: Checks if app directories exist (unless forced)
- Settings organization: Maintains clean, organized settings files
- Safety: Dry-run mode for previewing changes
Purpose: Remove all __pycache__ directories from all Django apps in the project to clean up compiled Python bytecode files.
Use Cases:
- Cleaning up before deployment
- Resolving import issues caused by stale bytecode
- Reducing project size
- Ensuring fresh Python imports
- Maintenance tasks
Examples:
# Remove all __pycache__ directories
uv run python manage.py cleanuppycache
# Preview what would be removed without deleting
uv run python manage.py cleanuppycache --dry-run
# Verbose output showing each directory as it's removed
uv run python manage.py cleanuppycache --verbose
# Combine dry-run with verbose for detailed preview
uv run python manage.py cleanuppycache --dry-run --verboseFeatures:
- Comprehensive cleanup: Finds and removes all
__pycache__directories - Safety: Dry-run mode to preview actions
- Verbose output: Shows each directory being processed
- Project-wide: Covers all Django apps in the project
- Core:
django,djangorestframework,django-filter,drf-extensions - Auth:
djangorestframework-simplejwt,djoser - Jobs:
celery,django-celery-beat,redis - DB/Config:
dj-database-url,psycopg2-binary,django-environ - Admin/UI:
django-unfold,django-cors-headers - Docs:
drf-spectacular - Logging/Debug:
loguru,icecream - Dev tools:
django-extensions,pytest,pytest-django,factory-boy,coverage,black,isort,mypy,ipython,werkzeug,django-silk,django-rosetta,pydantic
Defined in pyproject.toml with Python >=3.12. Locked versions live in uv.lock.
Container-first:
# install dev deps (already installed in image; re-run if needed)
docker compose exec web uv sync --group dev
# run tests
docker compose exec web uv run pytest -q
# coverage
docker compose exec web uv run coverage run -m pytest && \
docker compose exec web uv run coverage html
# format & lint
docker compose exec web uv run black .
docker compose exec web uv run isort .
# type check
docker compose exec web uv run mypy .Host mode: use the same commands without docker compose exec web.
This project uses a comprehensive testing setup with pytest, django-pytest, factory-boy, and coverage for robust test coverage.
Tests follow the same structure as the application code, mirroring the domain-driven architecture:
accounts/tests/
βββ factories/ # Factory-boy factories for test data
β βββ __init__.py
β βββ _user.py # User model factories
βββ models/
β βββ test_user.py # User model tests
βββ admin/
β βββ test_user_admin.py # Admin interface tests
βββ serializers/
β βββ test_auth.py # Serializer tests
βββ controllers/
β βββ test_auth.py # View/controller tests
βββ test_emails.py # Email functionality tests
βββ conftest.py # Pytest fixtures
βββ README.md # Detailed test documentation
# Run all accounts app tests with coverage
uv run pytest accounts/tests/ --cov=accounts --cov-report=term-missing
# Run specific test files
uv run pytest accounts/tests/controllers/test_auth.py
uv run pytest accounts/tests/serializers/test_auth.py
# Run specific test classes
uv run pytest accounts/tests/controllers/test_auth.py::TestCustomUserViewSet
# Run specific test methods
uv run pytest accounts/tests/controllers/test_auth.py::TestCustomUserViewSet::test_user_profile_access# Run all project tests with coverage
uv run pytest --ds=config.django.test --cov=. --cov-report=term-missing
# Run tests with HTML coverage report
uv run pytest --ds=config.django.test --cov=. --cov-report=html# Run tests inside Docker container
docker compose exec web uv run pytest
# Run tests with coverage in Docker
docker compose exec web uv run pytest --cov=accounts --cov-report=term-missing- Settings: Tests use
config.django.testsettings with in-memory SQLite database - Database: SQLite in-memory for fast test execution
- Coverage: Configured in
.coveragercto exclude test files and migrations - Factories: Located in
accounts/tests/factories/for generating test data
The project uses Factory-Boy to create test data efficiently:
from accounts.tests.factories import UserFactory, SuperUserFactory
# Create a basic user
user = UserFactory()
# Create a superuser
admin = SuperUserFactory()
# Create user with specific attributes
user = UserFactory(username="testuser", email="test@example.com")Available factories:
UserFactory: Basic user with default valuesInactiveUserFactory: User withis_active=FalseUnverifiedUserFactory: User withis_verified=FalseStaffUserFactory: Staff userSuperUserFactory: Superuser
The test suite provides comprehensive coverage for:
- Models: User model, custom manager, field validation
- Admin: UserAdmin configuration, list views, search functionality
- Serializers: Authentication, user creation, profile updates
- Controllers: JWT authentication, user management endpoints
- Emails: Custom email templates and integration
When adding new functionality, follow these patterns:
- Create factories in
accounts/tests/factories/for your models - Add model tests in
accounts/tests/models/ - Add admin tests in
accounts/tests/admin/ - Add serializer tests in
accounts/tests/serializers/ - Add controller tests in
accounts/tests/controllers/ - Use
@pytest.mark.django_dbfor tests requiring database access
Example test structure:
import pytest
from accounts.tests.factories import UserFactory
@pytest.mark.django_db
class TestMyFeature:
def test_something(self):
user = UserFactory()
# Test logic here
assert True- Use
@pytest.mark.django_dbfor database tests - Leverage factories for consistent test data
- Test both success and failure scenarios
- Use descriptive test names and docstrings
- Group related tests in classes
- Mock external dependencies when appropriate
- CORS defaults allow
http://localhost:8080andhttp://127.0.0.1:${WEB_PORT:-8000}. Updateconfig/settings/corsheaders.pyfor your frontend origins. - Always set
ALLOWED_HOSTSand secure secrets in production.
- Switch settings:
DJANGO_SETTINGS_MODULE=config.django.production - Use a real DB and Redis service. Static files are collected automatically when the web container starts (see
entrypoint.sh). If you deploy without that entrypoint (e.g. custom image or CI), run:docker compose exec web uv run python manage.py collectstatic --noinput - The web service runs Daphne (ASGI) with
--application-close-timeout 30. For production, put Daphne (or gunicorn/uvicorn) behind a reverse proxy (e.g. Nginx, Caddy).
- Commands failing on host: run them inside containers (
docker compose exec web ...), or switch to Host mode with correctDATABASE_URL/REDIS_URL. - Database connection errors: verify
DATABASE_URL. For SQLite, prefersqlite:///database/db.sqlite3and ensure the directory exists (Host mode). - 401 Unauthorized: obtain/refresh JWT via Djoser endpoints and send
Authorization: Bearer <token>. - Celery not picking tasks: ensure worker and beat are running and
REDIS_URLis reachable.