CtrlK
BlogDocsLog inGet started
Tessl Logo

django-project-starter

Scaffold a production-ready Django 5.x project with REST Framework, split settings, apps structure, and pytest-django.

66

Quality

57%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Optimize this skill with Tessl

npx tessl skill review --optimize ./backend-python/django-project-starter/SKILL.md
SKILL.md
Quality
Evals
Security

Django Project Starter

Scaffold a production-ready Django 5.x project with REST Framework, split settings, apps structure, and pytest-django.

Prerequisites

  • Python 3.12+
  • PostgreSQL 15+ (recommended for production)
  • uv or pip for dependency management

Scaffold Command

mkdir -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 Structure

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

Key Conventions

  • One Django app per bounded domain concept. Keep apps small and focused.
  • API code (serializers, viewsets, urls) lives in an api/ subpackage inside each app.
  • Settings are split: base.py holds shared config, dev.py/prod.py/test.py extend it.
  • Use django-environ to load environment variables. Never hardcode secrets.
  • Custom user model from day one. Changing later is painful.
  • Signals go in a dedicated signals.py, connected in apps.py via ready().
  • Tests live in a top-level tests/ directory mirroring app structure, using pytest-django.
  • Use factory-boy for test fixtures, not Django fixtures (JSON/YAML).
  • Django 5.x supports async views — use async def for I/O-heavy views and sync_to_async() for ORM calls in async contexts.

Essential Patterns

Split Settings (config/settings/base.py)

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"

Dev Settings (config/settings/dev.py)

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"]

Test Settings (config/settings/test.py)

from .base import *  # noqa: F401, F403

DEBUG = False
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": ":memory:",
    }
}
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]

Custom User Model (accounts/models.py)

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

Serializers (accounts/api/serializers.py)

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)

ViewSet (accounts/api/views.py)

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)

App URLs (accounts/api/urls.py)

from rest_framework.routers import DefaultRouter

from accounts.api.views import UserViewSet

router = DefaultRouter()
router.register("users", UserViewSet, basename="user")

urlpatterns = router.urls

Root URLs (config/urls.py)

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/v1/", include("accounts.api.urls")),
]

Signal (accounts/signals.py)

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

Register Signal (accounts/apps.py)

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

Admin (accounts/admin.py)

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"]

Management Command (accounts/management/commands/seed_users.py)

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"))

Test Conftest (tests/conftest.py)

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_client

Test Example (tests/accounts/test_api.py)

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

First Steps After Scaffold

  1. Copy .env.example to .env and fill in values
  2. Create virtual environment: python -m venv .venv && source .venv/bin/activate
  3. Install dependencies: pip install -e ".[dev]" (or uv sync)
  4. Run database migrations: python src/manage.py migrate
  5. Start dev server: DJANGO_SETTINGS_MODULE=config.settings.dev python src/manage.py runserver
  6. Test the API: curl http://localhost:8000/api/v1/users/

Common Commands

# 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

Integration Notes

  • Frontend (React/Next.js): Use DRF's browsable API during development. For production, add djangorestframework-simplejwt for token auth and configure CORS via django-cors-headers.
  • Docker: Use gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 as the production entrypoint. Run collectstatic at build time.
  • CI: Run python manage.py migrate --check (exits non-zero if unapplied migrations exist), then pytest.
  • Celery: Add celery[redis] and define config/celery.py. Import it in config/__init__.py so the app is loaded when Django starts.
  • Channels: For WebSocket support, add channels and daphne, switch to ASGI, and define consumers in each app.
Repository
achreftlili/deep-dev-skills
Last updated
Created

Is this your skill?

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.