backend-python/django-project-starter/SKILL.md
Scaffold a production-ready Django 5.x project with REST Framework, split settings, apps structure, and pytest-django.
npx skillsauth add achreftlili/deep-dev-skills django-project-starterInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Scaffold a production-ready Django 5.x project with REST Framework, split settings, apps structure, and pytest-django.
uv or pip for dependency managementmkdir -p src tests
cat > pyproject.toml << 'PYPROJECT'
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"django>=5.1",
"djangorestframework>=3.15",
"django-filter>=24.3",
"psycopg[binary]>=3.2",
"django-environ>=0.11",
"gunicorn>=23.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.3",
"pytest-django>=4.9",
"factory-boy>=3.3",
"ruff>=0.8",
"django-debug-toolbar>=4.4",
]
[tool.ruff]
target-version = "py312"
line-length = 99
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "DJ"]
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "config.settings.test"
python_files = ["tests.py", "test_*.py"]
testpaths = ["tests"]
PYPROJECT
# Bootstrap the Django project inside src/
cd src
django-admin startproject config .
mkdir -p config/settings
# Create the first app
python manage.py startapp accounts
# Initialize database
python manage.py migrate
project-root/
├── pyproject.toml
├── .env.example # Required env vars template
├── src/
│ ├── manage.py
│ ├── config/
│ │ ├── __init__.py
│ │ ├── urls.py
│ │ ├── wsgi.py
│ │ ├── asgi.py
│ │ └── settings/
│ │ ├── __init__.py # Imports from base
│ │ ├── base.py # Shared settings
│ │ ├── dev.py # DJANGO_SETTINGS_MODULE=config.settings.dev
│ │ ├── prod.py
│ │ └── test.py
│ └── accounts/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── signals.py
│ ├── management/
│ │ └── commands/
│ │ └── seed_users.py
│ └── api/
│ ├── __init__.py
│ ├── serializers.py
│ ├── views.py
│ ├── urls.py
│ └── filters.py
└── tests/
├── __init__.py
├── conftest.py
└── accounts/
├── __init__.py
├── test_models.py
└── test_api.py
api/ subpackage inside each app.base.py holds shared config, dev.py/prod.py/test.py extend it.django-environ to load environment variables. Never hardcode secrets.signals.py, connected in apps.py via ready().tests/ directory mirroring app structure, using pytest-django.factory-boy for test fixtures, not Django fixtures (JSON/YAML).async def for I/O-heavy views and sync_to_async() for ORM calls in async contexts.import environ
from pathlib import Path
env = environ.Env()
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = env("DJANGO_SECRET_KEY", default="change-me-in-production")
DEBUG = env.bool("DJANGO_DEBUG", default=False)
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[])
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# Third party
"rest_framework",
"django_filters",
# Local apps
"accounts",
]
AUTH_USER_MODEL = "accounts.User"
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
DATABASES = {
"default": env.db("DATABASE_URL", default="postgres://postgres:postgres@localhost:5432/myproject"),
}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 25,
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
],
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
from .base import * # noqa: F401, F403
from .base import INSTALLED_APPS, MIDDLEWARE
DEBUG = True
ALLOWED_HOSTS = ["*"]
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = ["127.0.0.1"]
from .base import * # noqa: F401, F403
DEBUG = False
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
}
}
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField(unique=True)
class Meta:
ordering = ["-date_joined"]
def __str__(self):
return self.email
from rest_framework import serializers
from accounts.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email", "first_name", "last_name", "date_joined"]
read_only_fields = ["id", "date_joined"]
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, min_length=8)
class Meta:
model = User
fields = ["username", "email", "password"]
def create(self, validated_data):
return User.objects.create_user(**validated_data)
from rest_framework import viewsets, mixins, status
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from accounts.models import User
from accounts.api.serializers import UserSerializer, UserCreateSerializer
class UserViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet,
):
queryset = User.objects.all()
serializer_class = UserSerializer
@action(detail=False, methods=["get"], permission_classes=[IsAuthenticated])
def me(self, request):
serializer = self.get_serializer(request.user)
return Response(serializer.data)
@action(detail=False, methods=["post"], permission_classes=[AllowAny])
def register(self, request):
serializer = UserCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
from rest_framework.routers import DefaultRouter
from accounts.api.views import UserViewSet
router = DefaultRouter()
router.register("users", UserViewSet, basename="user")
urlpatterns = router.urls
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("api/v1/", include("accounts.api.urls")),
]
from django.db.models.signals import post_save
from django.dispatch import receiver
from accounts.models import User
@receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs):
if created:
# e.g., create a profile, send welcome email, etc.
pass
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
def ready(self):
import accounts.signals # noqa: F401
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from accounts.models import User
@admin.register(User)
class UserAdmin(BaseUserAdmin):
list_display = ["email", "username", "is_staff", "date_joined"]
search_fields = ["email", "username"]
ordering = ["-date_joined"]
from django.core.management.base import BaseCommand
from accounts.models import User
class Command(BaseCommand):
help = "Seed the database with sample users"
def add_arguments(self, parser):
parser.add_argument("--count", type=int, default=10)
def handle(self, *args, **options):
count = options["count"]
users = [
User(username=f"user{i}", email=f"user{i}@example.com")
for i in range(count)
]
User.objects.bulk_create(users, ignore_conflicts=True)
self.stdout.write(self.style.SUCCESS(f"Seeded {count} users"))
import pytest
from rest_framework.test import APIClient
from accounts.models import User
@pytest.fixture
def api_client():
return APIClient()
@pytest.fixture
def user(db):
return User.objects.create_user(
username="testuser", email="[email protected]", password="testpass123"
)
@pytest.fixture
def auth_client(api_client, user):
api_client.force_authenticate(user=user)
return api_client
import pytest
from django.urls import reverse
@pytest.mark.django_db
class TestUserAPI:
def test_list_users_requires_auth(self, api_client):
response = api_client.get(reverse("user-list"))
assert response.status_code == 403
def test_me_returns_current_user(self, auth_client, user):
response = auth_client.get(reverse("user-me"))
assert response.status_code == 200
assert response.data["email"] == user.email
.env.example to .env and fill in valuespython -m venv .venv && source .venv/bin/activatepip install -e ".[dev]" (or uv sync)python src/manage.py migrateDJANGO_SETTINGS_MODULE=config.settings.dev python src/manage.py runservercurl http://localhost:8000/api/v1/users/# Install dependencies
uv sync # or: pip install -e ".[dev]"
# Run development server
DJANGO_SETTINGS_MODULE=config.settings.dev python src/manage.py runserver
# Create migrations
python src/manage.py makemigrations
# Apply migrations
python src/manage.py migrate
# Create superuser
python src/manage.py createsuperuser
# Run custom management command
python src/manage.py seed_users --count 50
# Run tests
pytest
# Lint and format
ruff check src tests
ruff format src tests
# Collect static files (production)
python src/manage.py collectstatic --noinput
djangorestframework-simplejwt for token auth and configure CORS via django-cors-headers.gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 as the production entrypoint. Run collectstatic at build time.python manage.py migrate --check (exits non-zero if unapplied migrations exist), then pytest.celery[redis] and define config/celery.py. Import it in config/__init__.py so the app is loaded when Django starts.channels and daphne, switch to ASGI, and define consumers in each app.testing
Set up Vitest 2.x with TypeScript for unit and component testing using test/describe/it, vi.fn/vi.mock/vi.spyOn, component testing with Testing Library, coverage (v8/istanbul), workspace config, and snapshot testing.
testing
Set up pytest 8.x with Python for unit and integration testing using fixtures (scope, autouse, parametrize), async tests (pytest-asyncio), mocking (unittest.mock, pytest-mock), coverage (pytest-cov), conftest.py patterns, and markers.
testing
Set up Playwright 1.49+ with TypeScript for E2E testing using page object model, fixtures, test.describe/test blocks, assertions, selectors, network mocking, CI configuration, and trace viewer.
testing
Set up Jest 30+ with TypeScript for unit tests, integration tests, mocking (jest.fn, jest.mock, jest.spyOn), coverage configuration, custom matchers, snapshot testing, and setup/teardown patterns.