Introduction: Python's Dominance in Web Development

Python has solidified its position as one of the most popular languages for web development, and for good reason. Its clean syntax, vast ecosystem, and two powerhouse frameworks — Django and FastAPI — make it possible to build everything from content management systems to high-performance APIs. Django, the "batteries included" framework, handles complex web applications with built-in authentication, admin panels, and ORM. FastAPI, the modern async framework, delivers blazing performance and automatic API documentation.

This guide walks you through building complete projects with both frameworks, compares them head-to-head, and shows you how to deploy to production.

Django: The Batteries-Included Framework

Project Setup from Scratch

## Create and activate a virtual environment
python -m venv venv
## Windows:
venvScriptsactivate
## Linux/Mac:
source venv/bin/activate

## Install Django and essential packages
pip install django djangorestframework django-cors-headers 
    django-filter celery redis pillow gunicorn psycopg2-binary

## Create a new project
django-admin startproject myproject .

## Create your first app
python manage.py startapp users
python manage.py startapp products
python manage.py startapp orders

## Project structure after setup:
## myproject/
## ├── manage.py
## ├── myproject/
## │   ├── __init__.py
## │   ├── settings.py
## │   ├── urls.py
## │   ├── asgi.py
## │   └── wsgi.py
## ├── users/
## ├── products/
## └── orders/

Settings Configuration

# myproject/settings.py
import os
from pathlib import Path
from datetime import timedelta

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-in-production")
DEBUG = os.environ.get("DEBUG", "True") == "True"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # Third-party
    "rest_framework",
    "corsheaders",
    "django_filters",
    # Local apps
    "users.apps.UsersConfig",
    "products.apps.ProductsConfig",
    "orders.apps.OrdersConfig",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "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",
]

# Database configuration
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("DB_NAME", "myproject"),
        "USER": os.environ.get("DB_USER", "postgres"),
        "PASSWORD": os.environ.get("DB_PASSWORD", "password"),
        "HOST": os.environ.get("DB_HOST", "localhost"),
        "PORT": os.environ.get("DB_PORT", "5432"),
    }
}

# REST Framework configuration
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],
}

# Celery configuration
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0")
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://localhost:6379/0")

# Static and media files
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

Models: The Database Layer

# products/models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from django.utils.text import slugify
import uuid


class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True, blank=True)
    description = models.TextField(blank=True)
    parent = models.ForeignKey(
        "self", on_delete=models.CASCADE,
        null=True, blank=True, related_name="children"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "categories"
        ordering = ["name"]

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name


class Product(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True, blank=True)
    description = models.TextField()
    price = models.DecimalField(
        max_digits=10, decimal_places=2,
        validators=[MinValueValidator(0.01)]
    )
    stock = models.PositiveIntegerField(default=0)
    category = models.ForeignKey(
        Category, on_delete=models.PROTECT, related_name="products"
    )
    image = models.ImageField(upload_to="products/%Y/%m/", blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, related_name="products"
    )

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["slug"]),
            models.Index(fields=["category", "is_active"]),
            models.Index(fields=["-created_at"]),
        ]

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    @property
    def is_in_stock(self):
        return self.stock > 0

    def __str__(self):
        return self.name
## Create and apply migrations
python manage.py makemigrations
python manage.py migrate

## Create a superuser for the admin panel
python manage.py createsuperuser

Admin Panel Customization

# products/admin.py
from django.contrib import admin
from .models import Product, Category


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ["name", "slug", "parent", "created_at"]
    prepopulated_fields = {"slug": ("name",)}
    search_fields = ["name"]


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ["name", "price", "stock", "category", "is_active", "created_at"]
    list_filter = ["is_active", "category", "created_at"]
    search_fields = ["name", "description"]
    prepopulated_fields = {"slug": ("name",)}
    list_editable = ["price", "stock", "is_active"]
    readonly_fields = ["created_at", "updated_at"]
    list_per_page = 25

    fieldsets = (
        (None, {"fields": ("name", "slug", "description", "category")}),
        ("Pricing & Stock", {"fields": ("price", "stock")}),
        ("Media", {"fields": ("image",)}),
        ("Status", {"fields": ("is_active",)}),
        ("Metadata", {"fields": ("created_at", "updated_at", "created_by"), "classes": ("collapse",)}),
    )

    def save_model(self, request, obj, form, change):
        if not change:  # Only set created_by on creation
            obj.created_by = request.user
        super().save_model(request, obj, form, change)

Django REST Framework: Serializers, ViewSets, Routers

# products/serializers.py
from rest_framework import serializers
from .models import Product, Category


class CategorySerializer(serializers.ModelSerializer):
    product_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Category
        fields = ["id", "name", "slug", "description", "parent", "product_count"]


class ProductSerializer(serializers.ModelSerializer):
    category_name = serializers.CharField(source="category.name", read_only=True)
    is_in_stock = serializers.BooleanField(read_only=True)

    class Meta:
        model = Product
        fields = [
            "id", "name", "slug", "description", "price",
            "stock", "category", "category_name", "image",
            "is_active", "is_in_stock", "created_at", "updated_at"
        ]
        read_only_fields = ["id", "slug", "created_at", "updated_at"]

    def validate_price(self, value):
        if value <= 0:
            raise serializers.ValidationError("Price must be greater than zero.")
        return value
# products/views.py
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from django.db.models import Count
from .models import Product, Category
from .serializers import ProductSerializer, CategorySerializer


class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.select_related("category", "created_by").filter(is_active=True)
    serializer_class = ProductSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ["category", "is_active"]
    search_fields = ["name", "description"]
    ordering_fields = ["price", "created_at", "name"]
    ordering = ["-created_at"]
    lookup_field = "slug"

    def perform_create(self, serializer):
        serializer.save(created_by=self.request.user)

    @action(detail=False, methods=["get"])
    def featured(self, request):
        featured = self.get_queryset().filter(stock__gt=0).order_by("-created_at")[:10]
        serializer = self.get_serializer(featured, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=["post"])
    def adjust_stock(self, request, slug=None):
        product = self.get_object()
        quantity = request.data.get("quantity", 0)
        product.stock += int(quantity)
        product.save()
        return Response({"stock": product.stock})


class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.annotate(product_count=Count("products"))
    serializer_class = CategorySerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    lookup_field = "slug"
# products/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet, CategoryViewSet

router = DefaultRouter()
router.register(r"products", ProductViewSet)
router.register(r"categories", CategoryViewSet)

urlpatterns = [
    path("", include(router.urls)),
]

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("products.urls")),
    path("api/auth/", include("rest_framework.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Django Signals

# products/signals.py
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.core.cache import cache
from .models import Product

@receiver(post_save, sender=Product)
def product_saved(sender, instance, created, **kwargs):
    # Invalidate cache when product is updated
    cache.delete(f"product_{instance.slug}")
    cache.delete("featured_products")
    if created:
        # Send notification for new products
        from .tasks import send_new_product_notification
        send_new_product_notification.delay(str(instance.id))

@receiver(pre_delete, sender=Product)
def product_deleting(sender, instance, **kwargs):
    # Clean up associated files
    if instance.image:
        instance.image.delete(save=False)

Celery Background Tasks

# myproject/celery.py
import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
app = Celery("myproject")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

# products/tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task(bind=True, max_retries=3)
def send_new_product_notification(self, product_id):
    try:
        from .models import Product
        product = Product.objects.get(id=product_id)
        send_mail(
            subject=f"New Product: {product.name}",
            message=f"Check out our new product: {product.name} - ${product.price}",
            from_email="noreply@example.com",
            recipient_list=["admin@example.com"],
        )
    except Exception as exc:
        self.retry(exc=exc, countdown=60)

@shared_task
def generate_daily_report():
    from .models import Product
    from django.utils import timezone
    from datetime import timedelta

    yesterday = timezone.now() - timedelta(days=1)
    new_products = Product.objects.filter(created_at__gte=yesterday).count()
    low_stock = Product.objects.filter(stock__lt=10, is_active=True).count()

    return {
        "new_products": new_products,
        "low_stock_alerts": low_stock,
        "generated_at": timezone.now().isoformat()
    }
## Start the Celery worker
celery -A myproject worker --loglevel=info

## Start Celery Beat for scheduled tasks
celery -A myproject beat --loglevel=info

## Run Django development server
python manage.py runserver

FastAPI: The Modern Async Framework

Project Setup

## Create project structure
mkdir fastapi-project && cd fastapi-project
python -m venv venv
source venv/bin/activate  # or venvScriptsactivate on Windows

## Install dependencies
pip install fastapi uvicorn[standard] sqlalchemy alembic 
    pydantic pydantic-settings python-jose[cryptography] 
    passlib[bcrypt] python-multipart aiofiles httpx

## Project structure:
## fastapi-project/
## ├── app/
## │   ├── __init__.py
## │   ├── main.py
## │   ├── config.py
## │   ├── database.py
## │   ├── models/
## │   ├── schemas/
## │   ├── routers/
## │   ├── services/
## │   ├── middleware/
## │   └── dependencies.py
## ├── alembic/
## ├── alembic.ini
## ├── requirements.txt
## └── tests/

Core Application with Dependency Injection

# app/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    app_name: str = "FastAPI Project"
    debug: bool = False
    database_url: str = "postgresql+asyncpg://user:pass@localhost/mydb"
    redis_url: str = "redis://localhost:6379/0"
    secret_key: str = "your-secret-key-here"
    algorithm: str = "HS256"
    access_token_expire_minutes: int = 30

    class Config:
        env_file = ".env"

@lru_cache
def get_settings():
    return Settings()
# app/database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from .config import get_settings

settings = get_settings()
engine = create_async_engine(settings.database_url, echo=settings.debug)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

class Base(DeclarativeBase):
    pass

async def get_db():
    async with async_session() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise
        finally:
            await session.close()
# app/models/product.py
import uuid
from sqlalchemy import Column, String, Float, Integer, Boolean, DateTime, ForeignKey, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from ..database import Base

class Product(Base):
    __tablename__ = "products"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = Column(String(200), nullable=False, index=True)
    slug = Column(String(200), unique=True, nullable=False, index=True)
    description = Column(Text, nullable=False)
    price = Column(Float, nullable=False)
    stock = Column(Integer, default=0)
    is_active = Column(Boolean, default=True)
    category_id = Column(UUID(as_uuid=True), ForeignKey("categories.id"))
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

    category = relationship("Category", back_populates="products")
# app/schemas/product.py
from pydantic import BaseModel, Field, field_validator
from uuid import UUID
from datetime import datetime
from typing import Optional

class ProductBase(BaseModel):
    name: str = Field(..., min_length=1, max_length=200)
    description: str = Field(..., min_length=10)
    price: float = Field(..., gt=0)
    stock: int = Field(default=0, ge=0)
    category_id: UUID
    is_active: bool = True

    @field_validator("name")
    @classmethod
    def name_must_not_be_empty(cls, v):
        if not v.strip():
            raise ValueError("Name cannot be empty or whitespace")
        return v.strip()

class ProductCreate(ProductBase):
    pass

class ProductUpdate(BaseModel):
    name: Optional[str] = Field(None, min_length=1, max_length=200)
    description: Optional[str] = None
    price: Optional[float] = Field(None, gt=0)
    stock: Optional[int] = Field(None, ge=0)
    is_active: Optional[bool] = None

class ProductResponse(ProductBase):
    id: UUID
    slug: str
    created_at: datetime
    updated_at: Optional[datetime] = None

    class Config:
        from_attributes = True
# app/routers/products.py
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from typing import Optional
from uuid import UUID
from ..database import get_db
from ..models.product import Product
from ..schemas.product import ProductCreate, ProductUpdate, ProductResponse
from ..dependencies import get_current_user
from slugify import slugify

router = APIRouter(prefix="/products", tags=["products"])

@router.get("/", response_model=list[ProductResponse])
async def list_products(
    skip: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    search: Optional[str] = None,
    category_id: Optional[UUID] = None,
    min_price: Optional[float] = None,
    max_price: Optional[float] = None,
    db: AsyncSession = Depends(get_db)
):
    query = select(Product).where(Product.is_active == True)

    if search:
        query = query.where(Product.name.ilike(f"%{search}%"))
    if category_id:
        query = query.where(Product.category_id == category_id)
    if min_price is not None:
        query = query.where(Product.price >= min_price)
    if max_price is not None:
        query = query.where(Product.price <= max_price)

    query = query.offset(skip).limit(limit).order_by(Product.created_at.desc())
    result = await db.execute(query)
    return result.scalars().all()

@router.post("/", response_model=ProductResponse, status_code=status.HTTP_201_CREATED)
async def create_product(
    product_data: ProductCreate,
    db: AsyncSession = Depends(get_db),
    current_user=Depends(get_current_user)
):
    product = Product(
        **product_data.model_dump(),
        slug=slugify(product_data.name)
    )
    db.add(product)
    await db.flush()
    await db.refresh(product)
    return product

@router.get("/{slug}", response_model=ProductResponse)
async def get_product(slug: str, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Product).where(Product.slug == slug))
    product = result.scalar_one_or_none()
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")
    return product

@router.patch("/{slug}", response_model=ProductResponse)
async def update_product(
    slug: str,
    product_data: ProductUpdate,
    db: AsyncSession = Depends(get_db),
    current_user=Depends(get_current_user)
):
    result = await db.execute(select(Product).where(Product.slug == slug))
    product = result.scalar_one_or_none()
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")

    update_data = product_data.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(product, field, value)

    await db.flush()
    await db.refresh(product)
    return product
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from .config import get_settings
from .database import engine, Base
from .routers import products, auth

settings = get_settings()

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: create tables
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    # Shutdown: dispose engine
    await engine.dispose()

app = FastAPI(
    title=settings.app_name,
    version="1.0.0",
    lifespan=lifespan
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(products.router, prefix="/api")
app.include_router(auth.router, prefix="/api")

@app.get("/healthz")
async def health_check():
    return {"status": "healthy"}
## Run the FastAPI development server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

## Access auto-generated API docs
## Swagger UI: http://localhost:8000/docs
## ReDoc: http://localhost:8000/redoc

JWT Authentication in FastAPI

# app/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from .config import get_settings

settings = get_settings()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
        user_id: str = payload.get("sub")
        if user_id is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    return user_id

WebSocket Support

# app/routers/websocket.py
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from typing import List

router = APIRouter()

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@router.websocket("/ws/notifications")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"Notification: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)

Alembic Migrations

## Initialize Alembic
alembic init alembic

## Generate a migration
alembic revision --autogenerate -m "create products table"

## Apply migrations
alembic upgrade head

## Rollback one step
alembic downgrade -1

## View migration history
alembic history

Django vs FastAPI: Detailed Comparison

FeatureDjangoFastAPI
ArchitectureMonolithic, batteries-includedMicro-framework, pick your tools
PerformanceSynchronous (async support added)Fully async, near Go/Node speeds
ORMBuilt-in Django ORMSQLAlchemy (manual setup)
Admin PanelBuilt-in, production-readyNone (use SQLAdmin or custom)
AuthenticationBuilt-in (sessions, tokens)Manual (JWT, OAuth2 helpers)
API DocumentationManual or drf-spectacularAutomatic Swagger + ReDoc
Type SafetyOptional type hintsPydantic enforced validation
Learning CurveModerate (many conventions)Lower (Python-native patterns)
WebSocket SupportVia Django ChannelsNative support
Template EngineBuilt-in Django TemplatesJinja2 (manual setup)
Background TasksCelery (separate process)Built-in + Celery option
Community/EcosystemMassive, 20+ yearsGrowing fast, modern
Best ForFull web apps, CMS, e-commerceAPIs, microservices, real-time apps

Deployment: Production Setup

Django with Gunicorn + Nginx

## Install Gunicorn
pip install gunicorn

## Run with Gunicorn (production)
gunicorn myproject.wsgi:application 
    --bind 0.0.0.0:8000 
    --workers 4 
    --threads 2 
    --timeout 120 
    --access-logfile - 
    --error-logfile -

## Collect static files
python manage.py collectstatic --noinput
# /etc/nginx/sites-available/myproject
server {
    listen 80;
    server_name example.com;

    location /static/ {
        alias /var/www/myproject/staticfiles/;
        expires 30d;
    }

    location /media/ {
        alias /var/www/myproject/media/;
        expires 7d;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

FastAPI with Uvicorn + Nginx

## Run with Uvicorn (production)
uvicorn app.main:app 
    --host 0.0.0.0 
    --port 8000 
    --workers 4 
    --loop uvloop 
    --http httptools 
    --access-log

Troubleshooting Common Issues

Problem: Django Migrations Conflict

Cause: Multiple developers created migrations simultaneously from different branches.

Solution:

## Merge conflicting migrations
python manage.py makemigrations --merge

## If that fails, reset migrations (DEVELOPMENT ONLY)
python manage.py migrate app_name zero
python manage.py makemigrations app_name
python manage.py migrate app_name

Problem: FastAPI Circular Import Error

Cause: Models import schemas that import models.

Solution: Use TYPE_CHECKING for type hints and lazy imports for runtime.

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .models import Product

Problem: N+1 Query Problem in Django

Cause: Accessing related objects in a loop without prefetching.

Solution:

# BAD: N+1 queries
products = Product.objects.all()
for p in products:
    print(p.category.name)  # Each access hits the database

# GOOD: 2 queries total
products = Product.objects.select_related("category").all()
for p in products:
    print(p.category.name)  # Already loaded

# For reverse/many-to-many relationships use prefetch_related
categories = Category.objects.prefetch_related("products").all()

Quick Reference Cheat Sheet

TaskDjango CommandFastAPI Equivalent
Start Projectdjango-admin startproject nameCreate main.py manually
Run Serverpython manage.py runserveruvicorn app.main:app --reload
Create Migrationpython manage.py makemigrationsalembic revision --autogenerate
Apply Migrationpython manage.py migratealembic upgrade head
Shellpython manage.py shellpython + manual imports
Create Adminpython manage.py createsuperuserManual setup required
Run Testspython manage.py testpytest
API DocsInstall drf-spectacularAutomatic at /docs