{
  "schemaVersion": "1.0",
  "item": {
    "slug": "afrexai-django-production",
    "name": "Django Production Engineering",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/1kalin/afrexai-django-production",
    "canonicalUrl": "https://clawhub.ai/1kalin/afrexai-django-production",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/afrexai-django-production",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-django-production",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/afrexai-django-production"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/afrexai-django-production",
    "agentPageUrl": "https://openagent3.xyz/skills/afrexai-django-production/agent",
    "manifestUrl": "https://openagent3.xyz/skills/afrexai-django-production/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/afrexai-django-production/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Django Production Engineering",
        "body": "Complete methodology for building, scaling, and operating production Django applications. From project structure to deployment, security to performance — every decision framework a Django team needs."
      },
      {
        "title": "Quick Health Check",
        "body": "Run this 8-signal triage on any Django project:\n\n#SignalCheckHealthy1Settings splitsettings/base.py, local.py, production.py exist✅ Split by env2Secret managementSECRET_KEY not in code, DEBUG=False in prod✅ Env vars / vault3DatabaseUsing connection pooling (pgbouncer / django-db-conn-pool)✅ Pool configured4Migrationspython manage.py showmigrations — no unapplied✅ All applied5Static filescollectstatic + CDN/whitenoise configured✅ Served properly6Async tasksCelery/django-q/Huey for background work✅ Not blocking views7CachingCache backend configured (Redis/Memcached)✅ Not DummyCache8Securitypython manage.py check --deploy passes✅ All checks pass\n\nScore: count ✅ / 8 — Below 6 = stop and fix foundations first."
      },
      {
        "title": "Recommended Structure",
        "body": "myproject/\n├── config/                    # Project config (was myproject/)\n│   ├── __init__.py\n│   ├── settings/\n│   │   ├── __init__.py\n│   │   ├── base.py           # Shared settings\n│   │   ├── local.py          # Development\n│   │   ├── staging.py        # Staging\n│   │   └── production.py     # Production\n│   ├── urls.py               # Root URL conf\n│   ├── wsgi.py\n│   ├── asgi.py\n│   └── celery.py             # Celery app\n├── apps/\n│   ├── users/                # Custom user model (ALWAYS)\n│   │   ├── models.py\n│   │   ├── managers.py\n│   │   ├── admin.py\n│   │   ├── serializers.py\n│   │   ├── views.py\n│   │   ├── urls.py\n│   │   ├── services.py       # Business logic\n│   │   ├── selectors.py      # Complex queries\n│   │   ├── tests/\n│   │   │   ├── test_models.py\n│   │   │   ├── test_views.py\n│   │   │   └── test_services.py\n│   │   └── migrations/\n│   ├── core/                 # Shared utilities\n│   │   ├── models.py         # Abstract base models\n│   │   ├── permissions.py\n│   │   ├── pagination.py\n│   │   ├── exceptions.py\n│   │   └── middleware.py\n│   └── <domain>/             # Feature apps\n├── templates/\n├── static/\n├── media/\n├── requirements/\n│   ├── base.txt\n│   ├── local.txt\n│   └── production.txt\n├── docker/\n├── scripts/\n├── manage.py\n├── pyproject.toml\n├── Makefile\n└── .env.example"
      },
      {
        "title": "7 Architecture Rules",
        "body": "Custom user model from Day 1 — AUTH_USER_MODEL = 'users.User'. Changing later is extremely painful.\nFat services, thin views — Views handle HTTP; services.py handles business logic; selectors.py handles complex queries.\nOne app per domain — Not per model. Group related models in one app.\nSettings split by environment — Never use if DEBUG conditionally in a single file.\nAbstract base models in core — TimeStampedModel, UUIDModel shared across apps.\nSeparate requirements files — base.txt (shared), local.txt (dev tools), production.txt (gunicorn, sentry).\nKeep apps decoupled — Apps communicate through services, not by importing each other's models directly. Use signals sparingly."
      },
      {
        "title": "Abstract Base Models",
        "body": "# apps/core/models.py\nimport uuid\nfrom django.db import models\n\nclass TimeStampedModel(models.Model):\n    created_at = models.DateTimeField(auto_now_add=True, db_index=True)\n    updated_at = models.DateTimeField(auto_now=True)\n\n    class Meta:\n        abstract = True\n\nclass UUIDModel(models.Model):\n    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)\n\n    class Meta:\n        abstract = True\n\nclass SoftDeleteModel(models.Model):\n    is_deleted = models.BooleanField(default=False, db_index=True)\n    deleted_at = models.DateTimeField(null=True, blank=True)\n\n    class Meta:\n        abstract = True\n\n    def soft_delete(self):\n        from django.utils import timezone\n        self.is_deleted = True\n        self.deleted_at = timezone.now()\n        self.save(update_fields=[\"is_deleted\", \"deleted_at\"])"
      },
      {
        "title": "N+1 Query Prevention",
        "body": "# ❌ N+1 — fires 1 + N queries\nfor order in Order.objects.all():\n    print(order.customer.name)        # Each access = new query\n    for item in order.items.all():    # Each access = new query\n        print(item.product.name)      # Each access = new query\n\n# ✅ Optimized — fires 3 queries total\norders = (\n    Order.objects\n    .select_related(\"customer\")              # FK/OneToOne — JOIN\n    .prefetch_related(\n        Prefetch(\n            \"items\",\n            queryset=OrderItem.objects\n                .select_related(\"product\")   # Nested FK\n                .only(\"id\", \"quantity\", \"product__name\")  # Only needed fields\n        )\n    )\n)"
      },
      {
        "title": "select_related vs prefetch_related Decision",
        "body": "RelationshipUseWhyForeignKey (forward)select_relatedSQL JOIN, single queryOneToOneFieldselect_relatedSQL JOIN, single queryManyToManyprefetch_relatedSeparate query, Python joinReverse FK (set)prefetch_relatedSeparate query, Python joinFiltered prefetchPrefetch() objectCustom queryset"
      },
      {
        "title": "QuerySet Evaluation Rules",
        "body": "# QuerySets are LAZY — no database hit until evaluated\nqs = Order.objects.filter(status=\"pending\")  # No query yet\n\n# These EVALUATE the queryset (trigger SQL):\nlist(qs)           # Iteration\nlen(qs)            # Use qs.count() instead\nbool(qs)           # Use qs.exists() instead\nqs[0]              # Indexing\nrepr(qs)           # In shell/debugger\nfor obj in qs:     # Iteration\nif qs:             # Use qs.exists()"
      },
      {
        "title": "Bulk Operations",
        "body": "# ❌ N queries\nfor item in items:\n    Product.objects.create(name=item[\"name\"], price=item[\"price\"])\n\n# ✅ 1 query\nProduct.objects.bulk_create(\n    [Product(name=i[\"name\"], price=i[\"price\"]) for i in items],\n    batch_size=1000,\n    ignore_conflicts=True,  # Skip duplicates\n)\n\n# ✅ Bulk update\nProduct.objects.filter(category=\"sale\").update(\n    price=F(\"price\") * 0.9  # 10% discount — single query, no race conditions\n)\n\n# ✅ Bulk update with different values\nproducts = Product.objects.filter(id__in=ids)\nfor p in products:\n    p.price = new_prices[p.id]\nProduct.objects.bulk_update(products, [\"price\"], batch_size=1000)"
      },
      {
        "title": "Database Functions & Expressions",
        "body": "from django.db.models import F, Q, Value, Count, Avg, Sum, Case, When\nfrom django.db.models.functions import Coalesce, Lower, TruncMonth\n\n# Conditional aggregation\nOrder.objects.aggregate(\n    total_revenue=Sum(\"amount\"),\n    paid_revenue=Sum(\"amount\", filter=Q(status=\"paid\")),\n    refund_count=Count(\"id\", filter=Q(status=\"refunded\")),\n    avg_order_value=Avg(\"amount\", filter=Q(status=\"paid\")),\n)\n\n# Annotate with computed fields\ncustomers = (\n    Customer.objects\n    .annotate(\n        order_count=Count(\"orders\"),\n        total_spent=Coalesce(Sum(\"orders__amount\"), Value(0)),\n        last_order=Max(\"orders__created_at\"),\n    )\n    .filter(order_count__gte=5)\n    .order_by(\"-total_spent\")\n)\n\n# Monthly revenue report\nmonthly = (\n    Order.objects\n    .filter(status=\"paid\")\n    .annotate(month=TruncMonth(\"created_at\"))\n    .values(\"month\")\n    .annotate(\n        revenue=Sum(\"amount\"),\n        count=Count(\"id\"),\n        avg=Avg(\"amount\"),\n    )\n    .order_by(\"month\")\n)\n\n# Case/When for computed status\nusers = User.objects.annotate(\n    tier=Case(\n        When(total_spent__gte=10000, then=Value(\"platinum\")),\n        When(total_spent__gte=5000, then=Value(\"gold\")),\n        When(total_spent__gte=1000, then=Value(\"silver\")),\n        default=Value(\"bronze\"),\n    )\n)"
      },
      {
        "title": "Index Strategy",
        "body": "class Order(TimeStampedModel):\n    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)\n    status = models.CharField(max_length=20, db_index=True)  # Single column\n    amount = models.DecimalField(max_digits=10, decimal_places=2)\n    \n    class Meta:\n        indexes = [\n            # Composite index — for queries filtering both\n            models.Index(fields=[\"status\", \"created_at\"], name=\"idx_order_status_created\"),\n            # Partial index — only index what you query\n            models.Index(\n                fields=[\"customer\"],\n                condition=Q(status=\"pending\"),\n                name=\"idx_order_pending_customer\",\n            ),\n            # Covering index (Postgres) — avoid table lookup\n            models.Index(\n                fields=[\"status\"],\n                include=[\"amount\", \"created_at\"],\n                name=\"idx_order_status_covering\",\n            ),\n        ]\n        # Default ordering impacts ALL queries — be intentional\n        ordering = [\"-created_at\"]"
      },
      {
        "title": "8 ORM Rules",
        "body": "Always use select_related/prefetch_related — install django-debug-toolbar and watch query count.\nNever use .count() + .all() together — use .exists() for boolean checks.\nUse F() expressions for atomic updates — avoids race conditions.\nUse .only() / .defer() for large text/JSON fields you don't need.\nUse .values() / .values_list() when you don't need model instances.\nUse iterator(chunk_size=2000) for large result sets — reduces memory.\nProfile with django-silk or django-debug-toolbar — never guess at performance.\nUse Exists() subqueries instead of __in with large lists."
      },
      {
        "title": "Serializer Patterns",
        "body": "# apps/orders/serializers.py\nfrom rest_framework import serializers\n\nclass OrderListSerializer(serializers.ModelSerializer):\n    \"\"\"Lightweight for list views — minimal fields.\"\"\"\n    customer_name = serializers.CharField(source=\"customer.name\", read_only=True)\n    \n    class Meta:\n        model = Order\n        fields = [\"id\", \"customer_name\", \"status\", \"amount\", \"created_at\"]\n        read_only_fields = [\"id\", \"created_at\"]\n\nclass OrderDetailSerializer(serializers.ModelSerializer):\n    \"\"\"Full detail with nested items.\"\"\"\n    items = OrderItemSerializer(many=True, read_only=True)\n    customer = CustomerSerializer(read_only=True)\n    \n    class Meta:\n        model = Order\n        fields = \"__all__\"\n\nclass OrderCreateSerializer(serializers.Serializer):\n    \"\"\"Explicit create — don't use ModelSerializer for writes.\"\"\"\n    customer_id = serializers.UUIDField()\n    items = OrderItemInputSerializer(many=True)\n    notes = serializers.CharField(required=False, allow_blank=True)\n    \n    def validate_items(self, value):\n        if not value:\n            raise serializers.ValidationError(\"At least one item required.\")\n        return value\n    \n    def create(self, validated_data):\n        # Delegate to service layer\n        from apps.orders.services import create_order\n        return create_order(**validated_data)"
      },
      {
        "title": "Service Layer Pattern",
        "body": "# apps/orders/services.py\nfrom django.db import transaction\nfrom django.core.exceptions import ValidationError\n\ndef create_order(*, customer_id: str, items: list[dict], notes: str = \"\") -> Order:\n    \"\"\"\n    Create order with items atomically.\n    \n    Raises:\n        ValidationError: If customer not found or insufficient stock.\n    \"\"\"\n    customer = Customer.objects.filter(id=customer_id).first()\n    if not customer:\n        raise ValidationError(\"Customer not found.\")\n    \n    with transaction.atomic():\n        order = Order.objects.create(customer=customer, notes=notes)\n        \n        order_items = []\n        for item_data in items:\n            product = Product.objects.select_for_update().get(id=item_data[\"product_id\"])\n            if product.stock < item_data[\"quantity\"]:\n                raise ValidationError(f\"Insufficient stock for {product.name}\")\n            \n            product.stock -= item_data[\"quantity\"]\n            product.save(update_fields=[\"stock\"])\n            \n            order_items.append(\n                OrderItem(order=order, product=product, quantity=item_data[\"quantity\"], unit_price=product.price)\n            )\n        \n        OrderItem.objects.bulk_create(order_items)\n        order.amount = sum(i.unit_price * i.quantity for i in order_items)\n        order.save(update_fields=[\"amount\"])\n    \n    # Side effects OUTSIDE transaction\n    send_order_confirmation.delay(order.id)\n    return order"
      },
      {
        "title": "ViewSet Best Practices",
        "body": "# apps/orders/views.py\nfrom rest_framework import viewsets, status\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\n\nclass OrderViewSet(viewsets.ModelViewSet):\n    permission_classes = [IsAuthenticated]\n    \n    def get_queryset(self):\n        \"\"\"Always scope to current user. Never return all objects.\"\"\"\n        return (\n            Order.objects\n            .filter(customer__user=self.request.user)\n            .select_related(\"customer\")\n            .prefetch_related(\"items__product\")\n        )\n    \n    def get_serializer_class(self):\n        \"\"\"Different serializers for different actions.\"\"\"\n        if self.action == \"list\":\n            return OrderListSerializer\n        if self.action in (\"create\",):\n            return OrderCreateSerializer\n        return OrderDetailSerializer\n    \n    def perform_create(self, serializer):\n        serializer.save()\n    \n    @action(detail=True, methods=[\"post\"])\n    def cancel(self, request, pk=None):\n        order = self.get_object()\n        from apps.orders.services import cancel_order\n        try:\n            cancel_order(order=order, cancelled_by=request.user)\n        except ValidationError as e:\n            return Response({\"error\": str(e)}, status=status.HTTP_400_BAD_REQUEST)\n        return Response({\"status\": \"cancelled\"})\n    \n    @action(detail=False, methods=[\"get\"])\n    def summary(self, request):\n        from apps.orders.selectors import get_order_summary\n        data = get_order_summary(user=request.user)\n        return Response(data)"
      },
      {
        "title": "Pagination",
        "body": "# apps/core/pagination.py\nfrom rest_framework.pagination import CursorPagination\n\nclass StandardCursorPagination(CursorPagination):\n    \"\"\"Cursor pagination — O(1) performance regardless of offset.\"\"\"\n    page_size = 25\n    page_size_query_param = \"page_size\"\n    max_page_size = 100\n    ordering = \"-created_at\"\n\n# settings/base.py\nREST_FRAMEWORK = {\n    \"DEFAULT_PAGINATION_CLASS\": \"apps.core.pagination.StandardCursorPagination\",\n    \"DEFAULT_THROTTLE_CLASSES\": [\n        \"rest_framework.throttling.AnonRateThrottle\",\n        \"rest_framework.throttling.UserRateThrottle\",\n    ],\n    \"DEFAULT_THROTTLE_RATES\": {\"anon\": \"100/hour\", \"user\": \"1000/hour\"},\n    \"DEFAULT_RENDERER_CLASSES\": [\"rest_framework.renderers.JSONRenderer\"],\n    \"DEFAULT_AUTHENTICATION_CLASSES\": [\n        \"rest_framework_simplejwt.authentication.JWTAuthentication\",\n    ],\n    \"EXCEPTION_HANDLER\": \"apps.core.exceptions.custom_exception_handler\",\n}"
      },
      {
        "title": "JWT Authentication (SimpleJWT)",
        "body": "# config/settings/base.py\nfrom datetime import timedelta\n\nSIMPLE_JWT = {\n    \"ACCESS_TOKEN_LIFETIME\": timedelta(minutes=15),\n    \"REFRESH_TOKEN_LIFETIME\": timedelta(days=7),\n    \"ROTATE_REFRESH_TOKENS\": True,\n    \"BLACKLIST_AFTER_ROTATION\": True,\n    \"ALGORITHM\": \"HS256\",\n    \"AUTH_HEADER_TYPES\": (\"Bearer\",),\n}\n\n# Custom user model\n# apps/users/models.py\nfrom django.contrib.auth.models import AbstractUser\nfrom apps.users.managers import UserManager\n\nclass User(AbstractUser):\n    username = None  # Remove username field\n    email = models.EmailField(unique=True)\n    \n    USERNAME_FIELD = \"email\"\n    REQUIRED_FIELDS = []\n    \n    objects = UserManager()"
      },
      {
        "title": "Security Settings (Production)",
        "body": "# config/settings/production.py\nimport os\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\nDEBUG = False\nALLOWED_HOSTS = os.environ[\"ALLOWED_HOSTS\"].split(\",\")\n\n# HTTPS\nSECURE_SSL_REDIRECT = True\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\n# Content security\nSECURE_CONTENT_TYPE_NOSNIFF = True\nSECURE_BROWSER_XSS_FILTER = True\nX_FRAME_OPTIONS = \"DENY\"\n\n# CORS (django-cors-headers)\nCORS_ALLOWED_ORIGINS = os.environ.get(\"CORS_ORIGINS\", \"\").split(\",\")\nCORS_ALLOW_CREDENTIALS = True\n\n# CSP (django-csp)\nCSP_DEFAULT_SRC = (\"'self'\",)\nCSP_SCRIPT_SRC = (\"'self'\",)\nCSP_STYLE_SRC = (\"'self'\", \"'unsafe-inline'\")\nCSP_IMG_SRC = (\"'self'\", \"data:\", \"https:\")"
      },
      {
        "title": "Permission Patterns",
        "body": "# apps/core/permissions.py\nfrom rest_framework.permissions import BasePermission\n\nclass IsOwner(BasePermission):\n    \"\"\"Object-level: only the owner can access.\"\"\"\n    def has_object_permission(self, request, view, obj):\n        return obj.user == request.user\n\nclass IsAdminOrReadOnly(BasePermission):\n    def has_permission(self, request, view):\n        if request.method in (\"GET\", \"HEAD\", \"OPTIONS\"):\n            return True\n        return request.user.is_staff\n\nclass HasRole(BasePermission):\n    \"\"\"Role-based access control.\"\"\"\n    required_role = None\n    \n    def has_permission(self, request, view):\n        if not request.user.is_authenticated:\n            return False\n        return request.user.roles.filter(name=self.required_role).exists()\n\nclass IsManager(HasRole):\n    required_role = \"manager\""
      },
      {
        "title": "10-Point Security Checklist",
        "body": "#CheckHowPriority1manage.py check --deployAll warnings resolvedP02SECRET_KEY from env/vaultNot in source codeP03DEBUG = False in productionEnv-specific settingsP04HTTPS enforcedSECURE_SSL_REDIRECT = TrueP05CSRF protection enabledDefault — don't disable itP06SQL injection preventedAlways use ORM, never raw SQL with f-stringsP07Input validationSerializer validation on all inputsP18Rate limitingDRF throttling configuredP19Admin URL changedNot /admin/ — use random pathP110Dependency auditpip-audit or safety check in CIP1"
      },
      {
        "title": "Migration Safety Rules",
        "body": "Never edit a migration after it's been applied in production — create a new one.\nAlways review auto-generated migrations — makemigrations can produce destructive changes.\nAdd columns as nullable first — null=True → deploy → backfill → make non-null.\nNever rename columns directly — add new, migrate data, remove old (3 deployments).\nUse RunPython with reverse_code — always make migrations reversible.\nSquash periodically — squashmigrations app_name 0001 0050 for performance.\nLock table awareness — ALTER TABLE ADD COLUMN NOT NULL DEFAULT locks the table in Postgres < 11."
      },
      {
        "title": "Zero-Downtime Migration Pattern",
        "body": "# Step 1: Add nullable column (safe, no lock)\n# migrations/0042_add_new_field.py\nclass Migration(migrations.Migration):\n    operations = [\n        migrations.AddField(\n            model_name=\"order\",\n            name=\"tracking_number\",\n            field=models.CharField(max_length=100, null=True, blank=True),\n        ),\n    ]\n\n# Step 2: Backfill data (separate migration)\n# migrations/0043_backfill_tracking.py\ndef backfill_tracking(apps, schema_editor):\n    Order = apps.get_model(\"orders\", \"Order\")\n    batch_size = 1000\n    while True:\n        ids = list(\n            Order.objects.filter(tracking_number__isnull=True)\n            .values_list(\"id\", flat=True)[:batch_size]\n        )\n        if not ids:\n            break\n        Order.objects.filter(id__in=ids).update(tracking_number=\"LEGACY\")\n\nclass Migration(migrations.Migration):\n    operations = [\n        migrations.RunPython(backfill_tracking, migrations.RunPython.noop),\n    ]\n\n# Step 3: Make non-null (after backfill verified)\n# migrations/0044_tracking_not_null.py"
      },
      {
        "title": "Migration Conflict Resolution",
        "body": "# When two developers create migrations from same parent:\npython manage.py makemigrations --merge  # Creates merge migration\n\n# To detect conflicts in CI:\npython manage.py makemigrations --check --dry-run"
      },
      {
        "title": "Cache Hierarchy",
        "body": "# config/settings/base.py\nCACHES = {\n    \"default\": {\n        \"BACKEND\": \"django_redis.cache.RedisCache\",\n        \"LOCATION\": os.environ.get(\"REDIS_URL\", \"redis://localhost:6379/0\"),\n        \"OPTIONS\": {\n            \"CLIENT_CLASS\": \"django_redis.client.DefaultClient\",\n            \"SERIALIZER\": \"django_redis.serializers.json.JSONSerializer\",\n        },\n        \"KEY_PREFIX\": \"myapp\",\n        \"TIMEOUT\": 300,  # 5 min default\n    }\n}\n\n# Session storage in Redis (faster than DB)\nSESSION_ENGINE = \"django.contrib.sessions.backends.cache\"\nSESSION_CACHE_ALIAS = \"default\""
      },
      {
        "title": "Caching Patterns",
        "body": "from django.core.cache import cache\nfrom django.views.decorators.cache import cache_page\nfrom django.utils.decorators import method_decorator\n\n# View-level caching\n@cache_page(60 * 15)  # 15 minutes\ndef product_list(request):\n    ...\n\n# Manual cache with invalidation\ndef get_product_stats(product_id: str) -> dict:\n    cache_key = f\"product_stats:{product_id}\"\n    stats = cache.get(cache_key)\n    if stats is None:\n        stats = _compute_product_stats(product_id)\n        cache.set(cache_key, stats, timeout=600)\n    return stats\n\ndef invalidate_product_cache(product_id: str):\n    cache.delete(f\"product_stats:{product_id}\")\n\n# Signal-based cache invalidation\nfrom django.db.models.signals import post_save, post_delete\nfrom django.dispatch import receiver\n\n@receiver([post_save, post_delete], sender=Product)\ndef clear_product_cache(sender, instance, **kwargs):\n    invalidate_product_cache(str(instance.id))\n    cache.delete(\"product_list\")\n\n# Template fragment caching\n# {% load cache %}\n# {% cache 600 sidebar request.user.id %}\n#   ... expensive template fragment ...\n# {% endcache %}"
      },
      {
        "title": "Cache Decision Guide",
        "body": "Data TypeStrategyTTLInvalidationUser sessionRedis session backend2 weeksOn logoutAPI list endpointcache_page5 minTime-basedComputed aggregationsManual cache.set10-30 minSignal on writePer-user dashboardManual with user key5 minOn user actionStatic config/settingsManual, long TTL1 hourOn admin saveFull-page (anonymous)Nginx/CDN1-60 minPurge API"
      },
      {
        "title": "Celery Configuration",
        "body": "# config/celery.py\nimport os\nfrom celery import Celery\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"config.settings.production\")\napp = Celery(\"myapp\")\napp.config_from_object(\"django.conf:settings\", namespace=\"CELERY\")\napp.autodiscover_tasks()\n\n# config/settings/base.py\nCELERY_BROKER_URL = os.environ.get(\"CELERY_BROKER_URL\", \"redis://localhost:6379/1\")\nCELERY_RESULT_BACKEND = os.environ.get(\"CELERY_RESULT_BACKEND\", \"redis://localhost:6379/2\")\nCELERY_ACCEPT_CONTENT = [\"json\"]\nCELERY_TASK_SERIALIZER = \"json\"\nCELERY_RESULT_SERIALIZER = \"json\"\nCELERY_TIMEZONE = \"UTC\"\nCELERY_TASK_TRACK_STARTED = True\nCELERY_TASK_TIME_LIMIT = 300  # 5 min hard limit\nCELERY_TASK_SOFT_TIME_LIMIT = 240  # 4 min soft limit\nCELERY_TASK_ACKS_LATE = True  # Re-deliver if worker crashes\nCELERY_WORKER_PREFETCH_MULTIPLIER = 1  # Fair scheduling"
      },
      {
        "title": "Task Patterns",
        "body": "# apps/orders/tasks.py\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\n\nlogger = get_task_logger(__name__)\n\n@shared_task(\n    bind=True,\n    max_retries=3,\n    default_retry_delay=60,\n    autoretry_for=(ConnectionError, TimeoutError),\n    retry_backoff=True,\n    retry_backoff_max=600,\n    acks_late=True,\n)\ndef send_order_confirmation(self, order_id: str):\n    \"\"\"Send confirmation email with exponential backoff retry.\"\"\"\n    try:\n        order = Order.objects.select_related(\"customer__user\").get(id=order_id)\n        send_email(\n            to=order.customer.user.email,\n            template=\"order_confirmation\",\n            context={\"order\": order},\n        )\n        logger.info(\"Confirmation sent\", extra={\"order_id\": order_id})\n    except Order.DoesNotExist:\n        logger.error(\"Order not found\", extra={\"order_id\": order_id})\n        # Don't retry — order doesn't exist\n\n@shared_task\ndef generate_daily_report():\n    \"\"\"Periodic task — scheduled via beat.\"\"\"\n    from apps.reports.services import build_daily_report\n    report = build_daily_report()\n    notify_admins(report)\n\n# Celery Beat schedule\nCELERY_BEAT_SCHEDULE = {\n    \"daily-report\": {\n        \"task\": \"apps.orders.tasks.generate_daily_report\",\n        \"schedule\": crontab(hour=6, minute=0),\n    },\n    \"cleanup-expired-sessions\": {\n        \"task\": \"apps.users.tasks.cleanup_sessions\",\n        \"schedule\": crontab(hour=3, minute=0),\n    },\n}"
      },
      {
        "title": "6 Celery Rules",
        "body": "Always pass IDs, not objects — task.delay(order.id) not task.delay(order). Objects can't serialize and may be stale.\nSet time limits — Every task needs time_limit to prevent zombies.\nMake tasks idempotent — They may run more than once (acks_late + crash = re-delivery).\nUse autoretry_for — Declarative retry for transient errors.\nSeparate queues — Critical tasks (payments) on different queue than bulk (emails).\nMonitor with Flower — celery -A config flower for real-time task monitoring."
      },
      {
        "title": "Test Pyramid",
        "body": "LevelToolTargetSpeedUnitpytestServices, utils, models<1s eachIntegrationpytest + Django test clientViews, serializers, DB<5s eachE2EPlaywright/SeleniumFull user flows<30s eachContractschemathesis/dreddAPI schema compliance<10s each"
      },
      {
        "title": "Testing Patterns",
        "body": "# conftest.py\nimport pytest\nfrom rest_framework.test import APIClient\n\n@pytest.fixture\ndef api_client():\n    return APIClient()\n\n@pytest.fixture\ndef authenticated_client(api_client, user):\n    api_client.force_authenticate(user=user)\n    return api_client\n\n@pytest.fixture\ndef user(db):\n    return User.objects.create_user(email=\"test@example.com\", password=\"testpass123\")\n\n@pytest.fixture\ndef order_factory(db):\n    def create(**kwargs):\n        defaults = {\"status\": \"pending\", \"amount\": 100}\n        defaults.update(kwargs)\n        if \"customer\" not in defaults:\n            defaults[\"customer\"] = CustomerFactory.create()\n        return Order.objects.create(**defaults)\n    return create\n\n# Test service layer\nclass TestCreateOrder:\n    def test_creates_order_with_items(self, db, customer, product):\n        order = create_order(\n            customer_id=customer.id,\n            items=[{\"product_id\": product.id, \"quantity\": 2}],\n        )\n        assert order.amount == product.price * 2\n        assert order.items.count() == 1\n        assert product.stock == Product.objects.get(id=product.id).stock  # Verify stock deducted\n\n    def test_rejects_insufficient_stock(self, db, customer, product):\n        product.stock = 0\n        product.save()\n        with pytest.raises(ValidationError, match=\"Insufficient stock\"):\n            create_order(\n                customer_id=customer.id,\n                items=[{\"product_id\": product.id, \"quantity\": 1}],\n            )\n\n# Test views\nclass TestOrderAPI:\n    def test_list_returns_only_user_orders(self, authenticated_client, user, order_factory):\n        my_order = order_factory(customer__user=user)\n        other_order = order_factory()  # Different user\n        \n        response = authenticated_client.get(\"/api/orders/\")\n        assert response.status_code == 200\n        ids = [o[\"id\"] for o in response.data[\"results\"]]\n        assert str(my_order.id) in ids\n        assert str(other_order.id) not in ids\n\n    def test_create_order_validates_items(self, authenticated_client):\n        response = authenticated_client.post(\"/api/orders/\", {\"items\": []}, format=\"json\")\n        assert response.status_code == 400"
      },
      {
        "title": "7 Testing Rules",
        "body": "Use pytest-django — not Django's TestCase. Faster, better fixtures.\nUse factories — factory_boy or custom fixtures. Never seed test DB manually.\nTest services, not views — Business logic in services = easy to test without HTTP.\nUse @pytest.mark.django_db — Only tests that need DB should hit it.\nFreeze time — freezegun for time-dependent logic.\nMock external services — responses for HTTP, unittest.mock for others.\nCI runs pytest --cov --cov-fail-under=80 — Enforce coverage floor."
      },
      {
        "title": "Gunicorn Configuration",
        "body": "# config/gunicorn.conf.py\nimport multiprocessing\n\nbind = \"0.0.0.0:8000\"\nworkers = multiprocessing.cpu_count() * 2 + 1\nworker_class = \"gthread\"  # or \"uvicorn.workers.UvicornWorker\" for async\nthreads = 4\nmax_requests = 1000\nmax_requests_jitter = 50\ntimeout = 30\ngraceful_timeout = 30\nkeepalive = 5\naccesslog = \"-\"\nerrorlog = \"-\"\nloglevel = \"info\""
      },
      {
        "title": "Django Middleware Order (Performance)",
        "body": "MIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",     # Static files (before everything)\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"corsheaders.middleware.CorsMiddleware\",           # CORS (before CommonMiddleware)\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"apps.core.middleware.RequestIDMiddleware\",         # Custom: attach request ID\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]"
      },
      {
        "title": "Structured Logging",
        "body": "# config/settings/base.py\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"json\": {\n            \"()\": \"pythonjsonlogger.jsonlogger.JsonFormatter\",\n            \"format\": \"%(asctime)s %(name)s %(levelname)s %(message)s\",\n        },\n    },\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"json\",\n        },\n    },\n    \"root\": {\"handlers\": [\"console\"], \"level\": \"INFO\"},\n    \"loggers\": {\n        \"django.db.backends\": {\"level\": \"WARNING\"},  # Quiet SQL logs\n        \"apps\": {\"level\": \"INFO\", \"propagate\": True},\n    },\n}"
      },
      {
        "title": "Performance Targets",
        "body": "MetricTargetHow to Measurep50 response time<100msdjango-silk / APMp99 response time<500msAPM (Sentry, Datadog)DB queries per request<10django-debug-toolbarMemory per worker<256MBGunicorn + monitoringCelery task latency<5sFlower / Prometheus"
      },
      {
        "title": "Production Dockerfile",
        "body": "# Multi-stage build\nFROM python:3.12-slim AS builder\nRUN pip install --no-cache-dir uv\nWORKDIR /app\nCOPY requirements/production.txt .\nRUN uv pip install --system --no-cache -r production.txt\n\nFROM python:3.12-slim\nRUN adduser --disabled-password --no-create-home app\nWORKDIR /app\n\nCOPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12\nCOPY --from=builder /usr/local/bin /usr/local/bin\nCOPY . .\n\nRUN python manage.py collectstatic --noinput\nUSER app\nEXPOSE 8000\nCMD [\"gunicorn\", \"config.wsgi:application\", \"-c\", \"config/gunicorn.conf.py\"]"
      },
      {
        "title": "GitHub Actions CI/CD",
        "body": "name: CI\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_DB: test_db\n          POSTGRES_USER: test\n          POSTGRES_PASSWORD: test\n        ports: [\"5432:5432\"]\n      redis:\n        image: redis:7\n        ports: [\"6379:6379\"]\n    \n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with: { python-version: \"3.12\" }\n      - run: pip install -r requirements/local.txt\n      - run: python manage.py check --deploy\n        env:\n          DJANGO_SETTINGS_MODULE: config.settings.local\n      - run: python manage.py makemigrations --check --dry-run\n      - run: pytest --cov --cov-fail-under=80 -n auto\n        env:\n          DATABASE_URL: postgres://test:test@localhost:5432/test_db\n          REDIS_URL: redis://localhost:6379/0\n      - run: ruff check .\n      - run: ruff format --check .\n      - run: mypy apps/"
      },
      {
        "title": "Production Checklist",
        "body": "P0 — Must have before deploy:\n\nmanage.py check --deploy passes with zero warnings\n SECRET_KEY from environment variable\n DEBUG = False\n ALLOWED_HOSTS configured\n HTTPS enforced\n Database connection pooling\n Static files served via WhiteNoise/CDN\n Error tracking (Sentry) configured\n Backups scheduled\n\nP1 — Should have within first week:\n\nRate limiting on all endpoints\n Admin URL changed from /admin/\n Cache backend configured (Redis)\n Background tasks via Celery\n Structured JSON logging\n CI/CD pipeline\n Health check endpoint\n pip-audit in CI"
      },
      {
        "title": "Soft Delete Manager",
        "body": "class ActiveManager(models.Manager):\n    def get_queryset(self):\n        return super().get_queryset().filter(is_deleted=False)\n\nclass Order(SoftDeleteModel):\n    objects = ActiveManager()      # Default: excludes deleted\n    all_objects = models.Manager() # Include deleted"
      },
      {
        "title": "Multi-Tenant Pattern",
        "body": "# Middleware: set tenant from request\nclass TenantMiddleware:\n    def __init__(self, get_response):\n        self.get_response = get_response\n    \n    def __call__(self, request):\n        tenant_id = request.headers.get(\"X-Tenant-ID\")\n        if tenant_id:\n            request.tenant = Tenant.objects.get(id=tenant_id)\n        return self.get_response(request)\n\n# Auto-filter all queries by tenant\nclass TenantManager(models.Manager):\n    def get_queryset(self):\n        from threading import local\n        _thread_local = local()\n        qs = super().get_queryset()\n        tenant = getattr(_thread_local, \"tenant\", None)\n        if tenant:\n            qs = qs.filter(tenant=tenant)\n        return qs"
      },
      {
        "title": "Webhook Handler",
        "body": "import hashlib, hmac\nfrom django.http import JsonResponse\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.decorators.http import require_POST\n\n@csrf_exempt\n@require_POST\ndef stripe_webhook(request):\n    payload = request.body\n    sig = request.headers.get(\"Stripe-Signature\")\n    \n    try:\n        event = stripe.Webhook.construct_event(payload, sig, settings.STRIPE_WEBHOOK_SECRET)\n    except (ValueError, stripe.error.SignatureVerificationError):\n        return JsonResponse({\"error\": \"Invalid signature\"}, status=400)\n    \n    handlers = {\n        \"checkout.session.completed\": handle_checkout,\n        \"invoice.paid\": handle_invoice_paid,\n        \"customer.subscription.deleted\": handle_cancellation,\n    }\n    \n    handler = handlers.get(event[\"type\"])\n    if handler:\n        handler(event[\"data\"][\"object\"])\n    \n    return JsonResponse({\"status\": \"ok\"})"
      },
      {
        "title": "GeneratedField (Django 5.0+)",
        "body": "class Product(models.Model):\n    price = models.DecimalField(max_digits=10, decimal_places=2)\n    tax_rate = models.DecimalField(max_digits=4, decimal_places=2, default=0.20)\n    \n    total_price = models.GeneratedField(\n        expression=F(\"price\") * (1 + F(\"tax_rate\")),\n        output_field=models.DecimalField(max_digits=10, decimal_places=2),\n        db_persist=True,  # Stored column, not virtual\n    )"
      },
      {
        "title": "Field Groups in Forms (Django 5.0+)",
        "body": "class ContactForm(forms.Form):\n    name = forms.CharField()\n    email = forms.EmailField()\n    \n    # Template: {{ form.as_field_group }}"
      },
      {
        "title": "Database-Computed Default (Django 5.0+)",
        "body": "from django.db.models.functions import Now\n\nclass Event(models.Model):\n    starts_at = models.DateTimeField(db_default=Now())"
      },
      {
        "title": "10 Common Mistakes",
        "body": "#MistakeFix1Not using custom User modelAlways AbstractUser from Day 12N+1 queries everywhereselect_related / prefetch_related3Business logic in viewsMove to services.py4One settings fileSplit: base.py, local.py, production.py5No migration reviewAlways read auto-generated migrations6DEBUG = True in productionEnv-specific settings, never conditional7Synchronous email sendingCelery task for all I/O8No connection poolingpgbouncer or django-db-conn-pool9Raw SQL with f-stringsORM or parameterized queries only10No request timeoutsGunicorn timeout + DB statement_timeout"
      },
      {
        "title": "Quality Rubric (0-100)",
        "body": "DimensionWeightCriteriaArchitecture15%Settings split, service layer, app structureORM Usage15%No N+1, bulk ops, proper indexesSecurity15%check --deploy, HTTPS, auth, CSRFTesting15%>80% coverage, pytest, factoriesPerformance10%Caching, connection pooling, query countError Handling10%Structured errors, Sentry, loggingMigrations10%Reversible, zero-downtime, reviewedDeployment10%Docker, CI/CD, health checks\n\n90-100: Production-grade, enterprise-ready\n70-89: Solid, needs minor hardening\n50-69: Functional but risky at scale\nBelow 50: Technical debt crisis — stop features, fix foundations"
      },
      {
        "title": "10 Commandments of Production Django",
        "body": "Custom User model from the first makemigrations.\nFat services, thin views. Always.\nselect_related and prefetch_related on every queryset with relations.\nSettings split by environment. No if DEBUG.\nEvery migration reviewed by a human before merge.\nCelery for anything that takes >200ms.\nmanage.py check --deploy in CI. Zero warnings.\nConnection pooling. Always.\nTest services, not implementation details.\nIf it's not in requirements.txt, it doesn't exist."
      },
      {
        "title": "Natural Language Commands",
        "body": "\"Review this Django project\" → Run Quick Health Check, score /8\n\"Optimize these queries\" → Apply N+1 prevention patterns, suggest indexes\n\"Set up DRF for this model\" → Generate serializers + views + URLs + tests\n\"Add Celery task for X\" → Task with retry, time limit, idempotency\n\"Review this migration\" → Check safety rules, suggest zero-downtime approach\n\"Set up authentication\" → JWT + custom user + permissions\n\"Production checklist\" → Run full P0/P1 audit\n\"Add caching for X\" → Pick strategy from cache decision guide\n\"Set up CI/CD\" → GitHub Actions + pytest + ruff + mypy\n\"Create service for X\" → Service layer with validation + transaction\n\"Set up logging\" → Structured JSON + request ID middleware\n\"Deploy this Django app\" → Dockerfile + gunicorn + checklist"
      }
    ],
    "body": "Django Production Engineering\n\nComplete methodology for building, scaling, and operating production Django applications. From project structure to deployment, security to performance — every decision framework a Django team needs.\n\nQuick Health Check\n\nRun this 8-signal triage on any Django project:\n\n#\tSignal\tCheck\tHealthy\n1\tSettings split\tsettings/base.py, local.py, production.py exist\t✅ Split by env\n2\tSecret management\tSECRET_KEY not in code, DEBUG=False in prod\t✅ Env vars / vault\n3\tDatabase\tUsing connection pooling (pgbouncer / django-db-conn-pool)\t✅ Pool configured\n4\tMigrations\tpython manage.py showmigrations — no unapplied\t✅ All applied\n5\tStatic files\tcollectstatic + CDN/whitenoise configured\t✅ Served properly\n6\tAsync tasks\tCelery/django-q/Huey for background work\t✅ Not blocking views\n7\tCaching\tCache backend configured (Redis/Memcached)\t✅ Not DummyCache\n8\tSecurity\tpython manage.py check --deploy passes\t✅ All checks pass\n\nScore: count ✅ / 8 — Below 6 = stop and fix foundations first.\n\nPhase 1: Project Architecture\nRecommended Structure\nmyproject/\n├── config/                    # Project config (was myproject/)\n│   ├── __init__.py\n│   ├── settings/\n│   │   ├── __init__.py\n│   │   ├── base.py           # Shared settings\n│   │   ├── local.py          # Development\n│   │   ├── staging.py        # Staging\n│   │   └── production.py     # Production\n│   ├── urls.py               # Root URL conf\n│   ├── wsgi.py\n│   ├── asgi.py\n│   └── celery.py             # Celery app\n├── apps/\n│   ├── users/                # Custom user model (ALWAYS)\n│   │   ├── models.py\n│   │   ├── managers.py\n│   │   ├── admin.py\n│   │   ├── serializers.py\n│   │   ├── views.py\n│   │   ├── urls.py\n│   │   ├── services.py       # Business logic\n│   │   ├── selectors.py      # Complex queries\n│   │   ├── tests/\n│   │   │   ├── test_models.py\n│   │   │   ├── test_views.py\n│   │   │   └── test_services.py\n│   │   └── migrations/\n│   ├── core/                 # Shared utilities\n│   │   ├── models.py         # Abstract base models\n│   │   ├── permissions.py\n│   │   ├── pagination.py\n│   │   ├── exceptions.py\n│   │   └── middleware.py\n│   └── <domain>/             # Feature apps\n├── templates/\n├── static/\n├── media/\n├── requirements/\n│   ├── base.txt\n│   ├── local.txt\n│   └── production.txt\n├── docker/\n├── scripts/\n├── manage.py\n├── pyproject.toml\n├── Makefile\n└── .env.example\n\n7 Architecture Rules\nCustom user model from Day 1 — AUTH_USER_MODEL = 'users.User'. Changing later is extremely painful.\nFat services, thin views — Views handle HTTP; services.py handles business logic; selectors.py handles complex queries.\nOne app per domain — Not per model. Group related models in one app.\nSettings split by environment — Never use if DEBUG conditionally in a single file.\nAbstract base models in core — TimeStampedModel, UUIDModel shared across apps.\nSeparate requirements files — base.txt (shared), local.txt (dev tools), production.txt (gunicorn, sentry).\nKeep apps decoupled — Apps communicate through services, not by importing each other's models directly. Use signals sparingly.\nAbstract Base Models\n# apps/core/models.py\nimport uuid\nfrom django.db import models\n\nclass TimeStampedModel(models.Model):\n    created_at = models.DateTimeField(auto_now_add=True, db_index=True)\n    updated_at = models.DateTimeField(auto_now=True)\n\n    class Meta:\n        abstract = True\n\nclass UUIDModel(models.Model):\n    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)\n\n    class Meta:\n        abstract = True\n\nclass SoftDeleteModel(models.Model):\n    is_deleted = models.BooleanField(default=False, db_index=True)\n    deleted_at = models.DateTimeField(null=True, blank=True)\n\n    class Meta:\n        abstract = True\n\n    def soft_delete(self):\n        from django.utils import timezone\n        self.is_deleted = True\n        self.deleted_at = timezone.now()\n        self.save(update_fields=[\"is_deleted\", \"deleted_at\"])\n\nPhase 2: ORM Mastery & Query Optimization\nN+1 Query Prevention\n# ❌ N+1 — fires 1 + N queries\nfor order in Order.objects.all():\n    print(order.customer.name)        # Each access = new query\n    for item in order.items.all():    # Each access = new query\n        print(item.product.name)      # Each access = new query\n\n# ✅ Optimized — fires 3 queries total\norders = (\n    Order.objects\n    .select_related(\"customer\")              # FK/OneToOne — JOIN\n    .prefetch_related(\n        Prefetch(\n            \"items\",\n            queryset=OrderItem.objects\n                .select_related(\"product\")   # Nested FK\n                .only(\"id\", \"quantity\", \"product__name\")  # Only needed fields\n        )\n    )\n)\n\nselect_related vs prefetch_related Decision\nRelationship\tUse\tWhy\nForeignKey (forward)\tselect_related\tSQL JOIN, single query\nOneToOneField\tselect_related\tSQL JOIN, single query\nManyToMany\tprefetch_related\tSeparate query, Python join\nReverse FK (set)\tprefetch_related\tSeparate query, Python join\nFiltered prefetch\tPrefetch() object\tCustom queryset\nQuerySet Evaluation Rules\n# QuerySets are LAZY — no database hit until evaluated\nqs = Order.objects.filter(status=\"pending\")  # No query yet\n\n# These EVALUATE the queryset (trigger SQL):\nlist(qs)           # Iteration\nlen(qs)            # Use qs.count() instead\nbool(qs)           # Use qs.exists() instead\nqs[0]              # Indexing\nrepr(qs)           # In shell/debugger\nfor obj in qs:     # Iteration\nif qs:             # Use qs.exists()\n\nBulk Operations\n# ❌ N queries\nfor item in items:\n    Product.objects.create(name=item[\"name\"], price=item[\"price\"])\n\n# ✅ 1 query\nProduct.objects.bulk_create(\n    [Product(name=i[\"name\"], price=i[\"price\"]) for i in items],\n    batch_size=1000,\n    ignore_conflicts=True,  # Skip duplicates\n)\n\n# ✅ Bulk update\nProduct.objects.filter(category=\"sale\").update(\n    price=F(\"price\") * 0.9  # 10% discount — single query, no race conditions\n)\n\n# ✅ Bulk update with different values\nproducts = Product.objects.filter(id__in=ids)\nfor p in products:\n    p.price = new_prices[p.id]\nProduct.objects.bulk_update(products, [\"price\"], batch_size=1000)\n\nDatabase Functions & Expressions\nfrom django.db.models import F, Q, Value, Count, Avg, Sum, Case, When\nfrom django.db.models.functions import Coalesce, Lower, TruncMonth\n\n# Conditional aggregation\nOrder.objects.aggregate(\n    total_revenue=Sum(\"amount\"),\n    paid_revenue=Sum(\"amount\", filter=Q(status=\"paid\")),\n    refund_count=Count(\"id\", filter=Q(status=\"refunded\")),\n    avg_order_value=Avg(\"amount\", filter=Q(status=\"paid\")),\n)\n\n# Annotate with computed fields\ncustomers = (\n    Customer.objects\n    .annotate(\n        order_count=Count(\"orders\"),\n        total_spent=Coalesce(Sum(\"orders__amount\"), Value(0)),\n        last_order=Max(\"orders__created_at\"),\n    )\n    .filter(order_count__gte=5)\n    .order_by(\"-total_spent\")\n)\n\n# Monthly revenue report\nmonthly = (\n    Order.objects\n    .filter(status=\"paid\")\n    .annotate(month=TruncMonth(\"created_at\"))\n    .values(\"month\")\n    .annotate(\n        revenue=Sum(\"amount\"),\n        count=Count(\"id\"),\n        avg=Avg(\"amount\"),\n    )\n    .order_by(\"month\")\n)\n\n# Case/When for computed status\nusers = User.objects.annotate(\n    tier=Case(\n        When(total_spent__gte=10000, then=Value(\"platinum\")),\n        When(total_spent__gte=5000, then=Value(\"gold\")),\n        When(total_spent__gte=1000, then=Value(\"silver\")),\n        default=Value(\"bronze\"),\n    )\n)\n\nIndex Strategy\nclass Order(TimeStampedModel):\n    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)\n    status = models.CharField(max_length=20, db_index=True)  # Single column\n    amount = models.DecimalField(max_digits=10, decimal_places=2)\n    \n    class Meta:\n        indexes = [\n            # Composite index — for queries filtering both\n            models.Index(fields=[\"status\", \"created_at\"], name=\"idx_order_status_created\"),\n            # Partial index — only index what you query\n            models.Index(\n                fields=[\"customer\"],\n                condition=Q(status=\"pending\"),\n                name=\"idx_order_pending_customer\",\n            ),\n            # Covering index (Postgres) — avoid table lookup\n            models.Index(\n                fields=[\"status\"],\n                include=[\"amount\", \"created_at\"],\n                name=\"idx_order_status_covering\",\n            ),\n        ]\n        # Default ordering impacts ALL queries — be intentional\n        ordering = [\"-created_at\"]\n\n8 ORM Rules\nAlways use select_related/prefetch_related — install django-debug-toolbar and watch query count.\nNever use .count() + .all() together — use .exists() for boolean checks.\nUse F() expressions for atomic updates — avoids race conditions.\nUse .only() / .defer() for large text/JSON fields you don't need.\nUse .values() / .values_list() when you don't need model instances.\nUse iterator(chunk_size=2000) for large result sets — reduces memory.\nProfile with django-silk or django-debug-toolbar — never guess at performance.\nUse Exists() subqueries instead of __in with large lists.\nPhase 3: Django REST Framework (DRF)\nSerializer Patterns\n# apps/orders/serializers.py\nfrom rest_framework import serializers\n\nclass OrderListSerializer(serializers.ModelSerializer):\n    \"\"\"Lightweight for list views — minimal fields.\"\"\"\n    customer_name = serializers.CharField(source=\"customer.name\", read_only=True)\n    \n    class Meta:\n        model = Order\n        fields = [\"id\", \"customer_name\", \"status\", \"amount\", \"created_at\"]\n        read_only_fields = [\"id\", \"created_at\"]\n\nclass OrderDetailSerializer(serializers.ModelSerializer):\n    \"\"\"Full detail with nested items.\"\"\"\n    items = OrderItemSerializer(many=True, read_only=True)\n    customer = CustomerSerializer(read_only=True)\n    \n    class Meta:\n        model = Order\n        fields = \"__all__\"\n\nclass OrderCreateSerializer(serializers.Serializer):\n    \"\"\"Explicit create — don't use ModelSerializer for writes.\"\"\"\n    customer_id = serializers.UUIDField()\n    items = OrderItemInputSerializer(many=True)\n    notes = serializers.CharField(required=False, allow_blank=True)\n    \n    def validate_items(self, value):\n        if not value:\n            raise serializers.ValidationError(\"At least one item required.\")\n        return value\n    \n    def create(self, validated_data):\n        # Delegate to service layer\n        from apps.orders.services import create_order\n        return create_order(**validated_data)\n\nService Layer Pattern\n# apps/orders/services.py\nfrom django.db import transaction\nfrom django.core.exceptions import ValidationError\n\ndef create_order(*, customer_id: str, items: list[dict], notes: str = \"\") -> Order:\n    \"\"\"\n    Create order with items atomically.\n    \n    Raises:\n        ValidationError: If customer not found or insufficient stock.\n    \"\"\"\n    customer = Customer.objects.filter(id=customer_id).first()\n    if not customer:\n        raise ValidationError(\"Customer not found.\")\n    \n    with transaction.atomic():\n        order = Order.objects.create(customer=customer, notes=notes)\n        \n        order_items = []\n        for item_data in items:\n            product = Product.objects.select_for_update().get(id=item_data[\"product_id\"])\n            if product.stock < item_data[\"quantity\"]:\n                raise ValidationError(f\"Insufficient stock for {product.name}\")\n            \n            product.stock -= item_data[\"quantity\"]\n            product.save(update_fields=[\"stock\"])\n            \n            order_items.append(\n                OrderItem(order=order, product=product, quantity=item_data[\"quantity\"], unit_price=product.price)\n            )\n        \n        OrderItem.objects.bulk_create(order_items)\n        order.amount = sum(i.unit_price * i.quantity for i in order_items)\n        order.save(update_fields=[\"amount\"])\n    \n    # Side effects OUTSIDE transaction\n    send_order_confirmation.delay(order.id)\n    return order\n\nViewSet Best Practices\n# apps/orders/views.py\nfrom rest_framework import viewsets, status\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\n\nclass OrderViewSet(viewsets.ModelViewSet):\n    permission_classes = [IsAuthenticated]\n    \n    def get_queryset(self):\n        \"\"\"Always scope to current user. Never return all objects.\"\"\"\n        return (\n            Order.objects\n            .filter(customer__user=self.request.user)\n            .select_related(\"customer\")\n            .prefetch_related(\"items__product\")\n        )\n    \n    def get_serializer_class(self):\n        \"\"\"Different serializers for different actions.\"\"\"\n        if self.action == \"list\":\n            return OrderListSerializer\n        if self.action in (\"create\",):\n            return OrderCreateSerializer\n        return OrderDetailSerializer\n    \n    def perform_create(self, serializer):\n        serializer.save()\n    \n    @action(detail=True, methods=[\"post\"])\n    def cancel(self, request, pk=None):\n        order = self.get_object()\n        from apps.orders.services import cancel_order\n        try:\n            cancel_order(order=order, cancelled_by=request.user)\n        except ValidationError as e:\n            return Response({\"error\": str(e)}, status=status.HTTP_400_BAD_REQUEST)\n        return Response({\"status\": \"cancelled\"})\n    \n    @action(detail=False, methods=[\"get\"])\n    def summary(self, request):\n        from apps.orders.selectors import get_order_summary\n        data = get_order_summary(user=request.user)\n        return Response(data)\n\nPagination\n# apps/core/pagination.py\nfrom rest_framework.pagination import CursorPagination\n\nclass StandardCursorPagination(CursorPagination):\n    \"\"\"Cursor pagination — O(1) performance regardless of offset.\"\"\"\n    page_size = 25\n    page_size_query_param = \"page_size\"\n    max_page_size = 100\n    ordering = \"-created_at\"\n\n# settings/base.py\nREST_FRAMEWORK = {\n    \"DEFAULT_PAGINATION_CLASS\": \"apps.core.pagination.StandardCursorPagination\",\n    \"DEFAULT_THROTTLE_CLASSES\": [\n        \"rest_framework.throttling.AnonRateThrottle\",\n        \"rest_framework.throttling.UserRateThrottle\",\n    ],\n    \"DEFAULT_THROTTLE_RATES\": {\"anon\": \"100/hour\", \"user\": \"1000/hour\"},\n    \"DEFAULT_RENDERER_CLASSES\": [\"rest_framework.renderers.JSONRenderer\"],\n    \"DEFAULT_AUTHENTICATION_CLASSES\": [\n        \"rest_framework_simplejwt.authentication.JWTAuthentication\",\n    ],\n    \"EXCEPTION_HANDLER\": \"apps.core.exceptions.custom_exception_handler\",\n}\n\nPhase 4: Authentication & Security\nJWT Authentication (SimpleJWT)\n# config/settings/base.py\nfrom datetime import timedelta\n\nSIMPLE_JWT = {\n    \"ACCESS_TOKEN_LIFETIME\": timedelta(minutes=15),\n    \"REFRESH_TOKEN_LIFETIME\": timedelta(days=7),\n    \"ROTATE_REFRESH_TOKENS\": True,\n    \"BLACKLIST_AFTER_ROTATION\": True,\n    \"ALGORITHM\": \"HS256\",\n    \"AUTH_HEADER_TYPES\": (\"Bearer\",),\n}\n\n# Custom user model\n# apps/users/models.py\nfrom django.contrib.auth.models import AbstractUser\nfrom apps.users.managers import UserManager\n\nclass User(AbstractUser):\n    username = None  # Remove username field\n    email = models.EmailField(unique=True)\n    \n    USERNAME_FIELD = \"email\"\n    REQUIRED_FIELDS = []\n    \n    objects = UserManager()\n\nSecurity Settings (Production)\n# config/settings/production.py\nimport os\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\nDEBUG = False\nALLOWED_HOSTS = os.environ[\"ALLOWED_HOSTS\"].split(\",\")\n\n# HTTPS\nSECURE_SSL_REDIRECT = True\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\n# Content security\nSECURE_CONTENT_TYPE_NOSNIFF = True\nSECURE_BROWSER_XSS_FILTER = True\nX_FRAME_OPTIONS = \"DENY\"\n\n# CORS (django-cors-headers)\nCORS_ALLOWED_ORIGINS = os.environ.get(\"CORS_ORIGINS\", \"\").split(\",\")\nCORS_ALLOW_CREDENTIALS = True\n\n# CSP (django-csp)\nCSP_DEFAULT_SRC = (\"'self'\",)\nCSP_SCRIPT_SRC = (\"'self'\",)\nCSP_STYLE_SRC = (\"'self'\", \"'unsafe-inline'\")\nCSP_IMG_SRC = (\"'self'\", \"data:\", \"https:\")\n\nPermission Patterns\n# apps/core/permissions.py\nfrom rest_framework.permissions import BasePermission\n\nclass IsOwner(BasePermission):\n    \"\"\"Object-level: only the owner can access.\"\"\"\n    def has_object_permission(self, request, view, obj):\n        return obj.user == request.user\n\nclass IsAdminOrReadOnly(BasePermission):\n    def has_permission(self, request, view):\n        if request.method in (\"GET\", \"HEAD\", \"OPTIONS\"):\n            return True\n        return request.user.is_staff\n\nclass HasRole(BasePermission):\n    \"\"\"Role-based access control.\"\"\"\n    required_role = None\n    \n    def has_permission(self, request, view):\n        if not request.user.is_authenticated:\n            return False\n        return request.user.roles.filter(name=self.required_role).exists()\n\nclass IsManager(HasRole):\n    required_role = \"manager\"\n\n10-Point Security Checklist\n#\tCheck\tHow\tPriority\n1\tmanage.py check --deploy\tAll warnings resolved\tP0\n2\tSECRET_KEY from env/vault\tNot in source code\tP0\n3\tDEBUG = False in production\tEnv-specific settings\tP0\n4\tHTTPS enforced\tSECURE_SSL_REDIRECT = True\tP0\n5\tCSRF protection enabled\tDefault — don't disable it\tP0\n6\tSQL injection prevented\tAlways use ORM, never raw SQL with f-strings\tP0\n7\tInput validation\tSerializer validation on all inputs\tP1\n8\tRate limiting\tDRF throttling configured\tP1\n9\tAdmin URL changed\tNot /admin/ — use random path\tP1\n10\tDependency audit\tpip-audit or safety check in CI\tP1\nPhase 5: Migrations & Database Management\nMigration Safety Rules\nNever edit a migration after it's been applied in production — create a new one.\nAlways review auto-generated migrations — makemigrations can produce destructive changes.\nAdd columns as nullable first — null=True → deploy → backfill → make non-null.\nNever rename columns directly — add new, migrate data, remove old (3 deployments).\nUse RunPython with reverse_code — always make migrations reversible.\nSquash periodically — squashmigrations app_name 0001 0050 for performance.\nLock table awareness — ALTER TABLE ADD COLUMN NOT NULL DEFAULT locks the table in Postgres < 11.\nZero-Downtime Migration Pattern\n# Step 1: Add nullable column (safe, no lock)\n# migrations/0042_add_new_field.py\nclass Migration(migrations.Migration):\n    operations = [\n        migrations.AddField(\n            model_name=\"order\",\n            name=\"tracking_number\",\n            field=models.CharField(max_length=100, null=True, blank=True),\n        ),\n    ]\n\n# Step 2: Backfill data (separate migration)\n# migrations/0043_backfill_tracking.py\ndef backfill_tracking(apps, schema_editor):\n    Order = apps.get_model(\"orders\", \"Order\")\n    batch_size = 1000\n    while True:\n        ids = list(\n            Order.objects.filter(tracking_number__isnull=True)\n            .values_list(\"id\", flat=True)[:batch_size]\n        )\n        if not ids:\n            break\n        Order.objects.filter(id__in=ids).update(tracking_number=\"LEGACY\")\n\nclass Migration(migrations.Migration):\n    operations = [\n        migrations.RunPython(backfill_tracking, migrations.RunPython.noop),\n    ]\n\n# Step 3: Make non-null (after backfill verified)\n# migrations/0044_tracking_not_null.py\n\nMigration Conflict Resolution\n# When two developers create migrations from same parent:\npython manage.py makemigrations --merge  # Creates merge migration\n\n# To detect conflicts in CI:\npython manage.py makemigrations --check --dry-run\n\nPhase 6: Caching Strategy\nCache Hierarchy\n# config/settings/base.py\nCACHES = {\n    \"default\": {\n        \"BACKEND\": \"django_redis.cache.RedisCache\",\n        \"LOCATION\": os.environ.get(\"REDIS_URL\", \"redis://localhost:6379/0\"),\n        \"OPTIONS\": {\n            \"CLIENT_CLASS\": \"django_redis.client.DefaultClient\",\n            \"SERIALIZER\": \"django_redis.serializers.json.JSONSerializer\",\n        },\n        \"KEY_PREFIX\": \"myapp\",\n        \"TIMEOUT\": 300,  # 5 min default\n    }\n}\n\n# Session storage in Redis (faster than DB)\nSESSION_ENGINE = \"django.contrib.sessions.backends.cache\"\nSESSION_CACHE_ALIAS = \"default\"\n\nCaching Patterns\nfrom django.core.cache import cache\nfrom django.views.decorators.cache import cache_page\nfrom django.utils.decorators import method_decorator\n\n# View-level caching\n@cache_page(60 * 15)  # 15 minutes\ndef product_list(request):\n    ...\n\n# Manual cache with invalidation\ndef get_product_stats(product_id: str) -> dict:\n    cache_key = f\"product_stats:{product_id}\"\n    stats = cache.get(cache_key)\n    if stats is None:\n        stats = _compute_product_stats(product_id)\n        cache.set(cache_key, stats, timeout=600)\n    return stats\n\ndef invalidate_product_cache(product_id: str):\n    cache.delete(f\"product_stats:{product_id}\")\n\n# Signal-based cache invalidation\nfrom django.db.models.signals import post_save, post_delete\nfrom django.dispatch import receiver\n\n@receiver([post_save, post_delete], sender=Product)\ndef clear_product_cache(sender, instance, **kwargs):\n    invalidate_product_cache(str(instance.id))\n    cache.delete(\"product_list\")\n\n# Template fragment caching\n# {% load cache %}\n# {% cache 600 sidebar request.user.id %}\n#   ... expensive template fragment ...\n# {% endcache %}\n\nCache Decision Guide\nData Type\tStrategy\tTTL\tInvalidation\nUser session\tRedis session backend\t2 weeks\tOn logout\nAPI list endpoint\tcache_page\t5 min\tTime-based\nComputed aggregations\tManual cache.set\t10-30 min\tSignal on write\nPer-user dashboard\tManual with user key\t5 min\tOn user action\nStatic config/settings\tManual, long TTL\t1 hour\tOn admin save\nFull-page (anonymous)\tNginx/CDN\t1-60 min\tPurge API\nPhase 7: Background Tasks (Celery)\nCelery Configuration\n# config/celery.py\nimport os\nfrom celery import Celery\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"config.settings.production\")\napp = Celery(\"myapp\")\napp.config_from_object(\"django.conf:settings\", namespace=\"CELERY\")\napp.autodiscover_tasks()\n\n# config/settings/base.py\nCELERY_BROKER_URL = os.environ.get(\"CELERY_BROKER_URL\", \"redis://localhost:6379/1\")\nCELERY_RESULT_BACKEND = os.environ.get(\"CELERY_RESULT_BACKEND\", \"redis://localhost:6379/2\")\nCELERY_ACCEPT_CONTENT = [\"json\"]\nCELERY_TASK_SERIALIZER = \"json\"\nCELERY_RESULT_SERIALIZER = \"json\"\nCELERY_TIMEZONE = \"UTC\"\nCELERY_TASK_TRACK_STARTED = True\nCELERY_TASK_TIME_LIMIT = 300  # 5 min hard limit\nCELERY_TASK_SOFT_TIME_LIMIT = 240  # 4 min soft limit\nCELERY_TASK_ACKS_LATE = True  # Re-deliver if worker crashes\nCELERY_WORKER_PREFETCH_MULTIPLIER = 1  # Fair scheduling\n\nTask Patterns\n# apps/orders/tasks.py\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\n\nlogger = get_task_logger(__name__)\n\n@shared_task(\n    bind=True,\n    max_retries=3,\n    default_retry_delay=60,\n    autoretry_for=(ConnectionError, TimeoutError),\n    retry_backoff=True,\n    retry_backoff_max=600,\n    acks_late=True,\n)\ndef send_order_confirmation(self, order_id: str):\n    \"\"\"Send confirmation email with exponential backoff retry.\"\"\"\n    try:\n        order = Order.objects.select_related(\"customer__user\").get(id=order_id)\n        send_email(\n            to=order.customer.user.email,\n            template=\"order_confirmation\",\n            context={\"order\": order},\n        )\n        logger.info(\"Confirmation sent\", extra={\"order_id\": order_id})\n    except Order.DoesNotExist:\n        logger.error(\"Order not found\", extra={\"order_id\": order_id})\n        # Don't retry — order doesn't exist\n\n@shared_task\ndef generate_daily_report():\n    \"\"\"Periodic task — scheduled via beat.\"\"\"\n    from apps.reports.services import build_daily_report\n    report = build_daily_report()\n    notify_admins(report)\n\n# Celery Beat schedule\nCELERY_BEAT_SCHEDULE = {\n    \"daily-report\": {\n        \"task\": \"apps.orders.tasks.generate_daily_report\",\n        \"schedule\": crontab(hour=6, minute=0),\n    },\n    \"cleanup-expired-sessions\": {\n        \"task\": \"apps.users.tasks.cleanup_sessions\",\n        \"schedule\": crontab(hour=3, minute=0),\n    },\n}\n\n6 Celery Rules\nAlways pass IDs, not objects — task.delay(order.id) not task.delay(order). Objects can't serialize and may be stale.\nSet time limits — Every task needs time_limit to prevent zombies.\nMake tasks idempotent — They may run more than once (acks_late + crash = re-delivery).\nUse autoretry_for — Declarative retry for transient errors.\nSeparate queues — Critical tasks (payments) on different queue than bulk (emails).\nMonitor with Flower — celery -A config flower for real-time task monitoring.\nPhase 8: Testing Strategy\nTest Pyramid\nLevel\tTool\tTarget\tSpeed\nUnit\tpytest\tServices, utils, models\t<1s each\nIntegration\tpytest + Django test client\tViews, serializers, DB\t<5s each\nE2E\tPlaywright/Selenium\tFull user flows\t<30s each\nContract\tschemathesis/dredd\tAPI schema compliance\t<10s each\nTesting Patterns\n# conftest.py\nimport pytest\nfrom rest_framework.test import APIClient\n\n@pytest.fixture\ndef api_client():\n    return APIClient()\n\n@pytest.fixture\ndef authenticated_client(api_client, user):\n    api_client.force_authenticate(user=user)\n    return api_client\n\n@pytest.fixture\ndef user(db):\n    return User.objects.create_user(email=\"test@example.com\", password=\"testpass123\")\n\n@pytest.fixture\ndef order_factory(db):\n    def create(**kwargs):\n        defaults = {\"status\": \"pending\", \"amount\": 100}\n        defaults.update(kwargs)\n        if \"customer\" not in defaults:\n            defaults[\"customer\"] = CustomerFactory.create()\n        return Order.objects.create(**defaults)\n    return create\n\n# Test service layer\nclass TestCreateOrder:\n    def test_creates_order_with_items(self, db, customer, product):\n        order = create_order(\n            customer_id=customer.id,\n            items=[{\"product_id\": product.id, \"quantity\": 2}],\n        )\n        assert order.amount == product.price * 2\n        assert order.items.count() == 1\n        assert product.stock == Product.objects.get(id=product.id).stock  # Verify stock deducted\n\n    def test_rejects_insufficient_stock(self, db, customer, product):\n        product.stock = 0\n        product.save()\n        with pytest.raises(ValidationError, match=\"Insufficient stock\"):\n            create_order(\n                customer_id=customer.id,\n                items=[{\"product_id\": product.id, \"quantity\": 1}],\n            )\n\n# Test views\nclass TestOrderAPI:\n    def test_list_returns_only_user_orders(self, authenticated_client, user, order_factory):\n        my_order = order_factory(customer__user=user)\n        other_order = order_factory()  # Different user\n        \n        response = authenticated_client.get(\"/api/orders/\")\n        assert response.status_code == 200\n        ids = [o[\"id\"] for o in response.data[\"results\"]]\n        assert str(my_order.id) in ids\n        assert str(other_order.id) not in ids\n\n    def test_create_order_validates_items(self, authenticated_client):\n        response = authenticated_client.post(\"/api/orders/\", {\"items\": []}, format=\"json\")\n        assert response.status_code == 400\n\n7 Testing Rules\nUse pytest-django — not Django's TestCase. Faster, better fixtures.\nUse factories — factory_boy or custom fixtures. Never seed test DB manually.\nTest services, not views — Business logic in services = easy to test without HTTP.\nUse @pytest.mark.django_db — Only tests that need DB should hit it.\nFreeze time — freezegun for time-dependent logic.\nMock external services — responses for HTTP, unittest.mock for others.\nCI runs pytest --cov --cov-fail-under=80 — Enforce coverage floor.\nPhase 9: Performance & Monitoring\nGunicorn Configuration\n# config/gunicorn.conf.py\nimport multiprocessing\n\nbind = \"0.0.0.0:8000\"\nworkers = multiprocessing.cpu_count() * 2 + 1\nworker_class = \"gthread\"  # or \"uvicorn.workers.UvicornWorker\" for async\nthreads = 4\nmax_requests = 1000\nmax_requests_jitter = 50\ntimeout = 30\ngraceful_timeout = 30\nkeepalive = 5\naccesslog = \"-\"\nerrorlog = \"-\"\nloglevel = \"info\"\n\nDjango Middleware Order (Performance)\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",     # Static files (before everything)\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"corsheaders.middleware.CorsMiddleware\",           # CORS (before CommonMiddleware)\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"apps.core.middleware.RequestIDMiddleware\",         # Custom: attach request ID\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nStructured Logging\n# config/settings/base.py\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"json\": {\n            \"()\": \"pythonjsonlogger.jsonlogger.JsonFormatter\",\n            \"format\": \"%(asctime)s %(name)s %(levelname)s %(message)s\",\n        },\n    },\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"json\",\n        },\n    },\n    \"root\": {\"handlers\": [\"console\"], \"level\": \"INFO\"},\n    \"loggers\": {\n        \"django.db.backends\": {\"level\": \"WARNING\"},  # Quiet SQL logs\n        \"apps\": {\"level\": \"INFO\", \"propagate\": True},\n    },\n}\n\nPerformance Targets\nMetric\tTarget\tHow to Measure\np50 response time\t<100ms\tdjango-silk / APM\np99 response time\t<500ms\tAPM (Sentry, Datadog)\nDB queries per request\t<10\tdjango-debug-toolbar\nMemory per worker\t<256MB\tGunicorn + monitoring\nCelery task latency\t<5s\tFlower / Prometheus\nPhase 10: Deployment\nProduction Dockerfile\n# Multi-stage build\nFROM python:3.12-slim AS builder\nRUN pip install --no-cache-dir uv\nWORKDIR /app\nCOPY requirements/production.txt .\nRUN uv pip install --system --no-cache -r production.txt\n\nFROM python:3.12-slim\nRUN adduser --disabled-password --no-create-home app\nWORKDIR /app\n\nCOPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12\nCOPY --from=builder /usr/local/bin /usr/local/bin\nCOPY . .\n\nRUN python manage.py collectstatic --noinput\nUSER app\nEXPOSE 8000\nCMD [\"gunicorn\", \"config.wsgi:application\", \"-c\", \"config/gunicorn.conf.py\"]\n\nGitHub Actions CI/CD\nname: CI\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_DB: test_db\n          POSTGRES_USER: test\n          POSTGRES_PASSWORD: test\n        ports: [\"5432:5432\"]\n      redis:\n        image: redis:7\n        ports: [\"6379:6379\"]\n    \n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with: { python-version: \"3.12\" }\n      - run: pip install -r requirements/local.txt\n      - run: python manage.py check --deploy\n        env:\n          DJANGO_SETTINGS_MODULE: config.settings.local\n      - run: python manage.py makemigrations --check --dry-run\n      - run: pytest --cov --cov-fail-under=80 -n auto\n        env:\n          DATABASE_URL: postgres://test:test@localhost:5432/test_db\n          REDIS_URL: redis://localhost:6379/0\n      - run: ruff check .\n      - run: ruff format --check .\n      - run: mypy apps/\n\nProduction Checklist\n\nP0 — Must have before deploy:\n\n manage.py check --deploy passes with zero warnings\n SECRET_KEY from environment variable\n DEBUG = False\n ALLOWED_HOSTS configured\n HTTPS enforced\n Database connection pooling\n Static files served via WhiteNoise/CDN\n Error tracking (Sentry) configured\n Backups scheduled\n\nP1 — Should have within first week:\n\n Rate limiting on all endpoints\n Admin URL changed from /admin/\n Cache backend configured (Redis)\n Background tasks via Celery\n Structured JSON logging\n CI/CD pipeline\n Health check endpoint\n pip-audit in CI\nPhase 11: Common Patterns Library\nSoft Delete Manager\nclass ActiveManager(models.Manager):\n    def get_queryset(self):\n        return super().get_queryset().filter(is_deleted=False)\n\nclass Order(SoftDeleteModel):\n    objects = ActiveManager()      # Default: excludes deleted\n    all_objects = models.Manager() # Include deleted\n\nMulti-Tenant Pattern\n# Middleware: set tenant from request\nclass TenantMiddleware:\n    def __init__(self, get_response):\n        self.get_response = get_response\n    \n    def __call__(self, request):\n        tenant_id = request.headers.get(\"X-Tenant-ID\")\n        if tenant_id:\n            request.tenant = Tenant.objects.get(id=tenant_id)\n        return self.get_response(request)\n\n# Auto-filter all queries by tenant\nclass TenantManager(models.Manager):\n    def get_queryset(self):\n        from threading import local\n        _thread_local = local()\n        qs = super().get_queryset()\n        tenant = getattr(_thread_local, \"tenant\", None)\n        if tenant:\n            qs = qs.filter(tenant=tenant)\n        return qs\n\nWebhook Handler\nimport hashlib, hmac\nfrom django.http import JsonResponse\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.decorators.http import require_POST\n\n@csrf_exempt\n@require_POST\ndef stripe_webhook(request):\n    payload = request.body\n    sig = request.headers.get(\"Stripe-Signature\")\n    \n    try:\n        event = stripe.Webhook.construct_event(payload, sig, settings.STRIPE_WEBHOOK_SECRET)\n    except (ValueError, stripe.error.SignatureVerificationError):\n        return JsonResponse({\"error\": \"Invalid signature\"}, status=400)\n    \n    handlers = {\n        \"checkout.session.completed\": handle_checkout,\n        \"invoice.paid\": handle_invoice_paid,\n        \"customer.subscription.deleted\": handle_cancellation,\n    }\n    \n    handler = handlers.get(event[\"type\"])\n    if handler:\n        handler(event[\"data\"][\"object\"])\n    \n    return JsonResponse({\"status\": \"ok\"})\n\nPhase 12: Django 5.x Features\nGeneratedField (Django 5.0+)\nclass Product(models.Model):\n    price = models.DecimalField(max_digits=10, decimal_places=2)\n    tax_rate = models.DecimalField(max_digits=4, decimal_places=2, default=0.20)\n    \n    total_price = models.GeneratedField(\n        expression=F(\"price\") * (1 + F(\"tax_rate\")),\n        output_field=models.DecimalField(max_digits=10, decimal_places=2),\n        db_persist=True,  # Stored column, not virtual\n    )\n\nField Groups in Forms (Django 5.0+)\nclass ContactForm(forms.Form):\n    name = forms.CharField()\n    email = forms.EmailField()\n    \n    # Template: {{ form.as_field_group }}\n\nDatabase-Computed Default (Django 5.0+)\nfrom django.db.models.functions import Now\n\nclass Event(models.Model):\n    starts_at = models.DateTimeField(db_default=Now())\n\n10 Common Mistakes\n#\tMistake\tFix\n1\tNot using custom User model\tAlways AbstractUser from Day 1\n2\tN+1 queries everywhere\tselect_related / prefetch_related\n3\tBusiness logic in views\tMove to services.py\n4\tOne settings file\tSplit: base.py, local.py, production.py\n5\tNo migration review\tAlways read auto-generated migrations\n6\tDEBUG = True in production\tEnv-specific settings, never conditional\n7\tSynchronous email sending\tCelery task for all I/O\n8\tNo connection pooling\tpgbouncer or django-db-conn-pool\n9\tRaw SQL with f-strings\tORM or parameterized queries only\n10\tNo request timeouts\tGunicorn timeout + DB statement_timeout\nQuality Rubric (0-100)\nDimension\tWeight\tCriteria\nArchitecture\t15%\tSettings split, service layer, app structure\nORM Usage\t15%\tNo N+1, bulk ops, proper indexes\nSecurity\t15%\tcheck --deploy, HTTPS, auth, CSRF\nTesting\t15%\t>80% coverage, pytest, factories\nPerformance\t10%\tCaching, connection pooling, query count\nError Handling\t10%\tStructured errors, Sentry, logging\nMigrations\t10%\tReversible, zero-downtime, reviewed\nDeployment\t10%\tDocker, CI/CD, health checks\n\n90-100: Production-grade, enterprise-ready 70-89: Solid, needs minor hardening 50-69: Functional but risky at scale Below 50: Technical debt crisis — stop features, fix foundations\n\n10 Commandments of Production Django\nCustom User model from the first makemigrations.\nFat services, thin views. Always.\nselect_related and prefetch_related on every queryset with relations.\nSettings split by environment. No if DEBUG.\nEvery migration reviewed by a human before merge.\nCelery for anything that takes >200ms.\nmanage.py check --deploy in CI. Zero warnings.\nConnection pooling. Always.\nTest services, not implementation details.\nIf it's not in requirements.txt, it doesn't exist.\nNatural Language Commands\n\"Review this Django project\" → Run Quick Health Check, score /8\n\"Optimize these queries\" → Apply N+1 prevention patterns, suggest indexes\n\"Set up DRF for this model\" → Generate serializers + views + URLs + tests\n\"Add Celery task for X\" → Task with retry, time limit, idempotency\n\"Review this migration\" → Check safety rules, suggest zero-downtime approach\n\"Set up authentication\" → JWT + custom user + permissions\n\"Production checklist\" → Run full P0/P1 audit\n\"Add caching for X\" → Pick strategy from cache decision guide\n\"Set up CI/CD\" → GitHub Actions + pytest + ruff + mypy\n\"Create service for X\" → Service layer with validation + transaction\n\"Set up logging\" → Structured JSON + request ID middleware\n\"Deploy this Django app\" → Dockerfile + gunicorn + checklist"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/1kalin/afrexai-django-production",
    "publisherUrl": "https://clawhub.ai/1kalin/afrexai-django-production",
    "owner": "1kalin",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/afrexai-django-production",
    "downloadUrl": "https://openagent3.xyz/downloads/afrexai-django-production",
    "agentUrl": "https://openagent3.xyz/skills/afrexai-django-production/agent",
    "manifestUrl": "https://openagent3.xyz/skills/afrexai-django-production/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/afrexai-django-production/agent.md"
  }
}