Scaffold a production-ready Django 5.x project with REST Framework, split settings, apps structure, and pytest-django.
66
57%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./backend-python/django-project-starter/SKILL.mdScaffold 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 migrateproject-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.pyapi/ 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.emailfrom 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.urlsfrom 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.
passfrom django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
def ready(self):
import accounts.signals # noqa: F401from 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="test@example.com", password="testpass123"
)
@pytest.fixture
def auth_client(api_client, user):
api_client.force_authenticate(user=user)
return api_clientimport 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 --noinputdjangorestframework-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.181fcbc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.