协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
django-best-practices【开发与工程】Implements Django 5.x application patterns including modern project structure, ORM optimization, class-based and function views, DRF integration, async views, caching strategies, and settings management for production-ready web applications.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: django-best-practices description: Implements Django 5.x application patterns including modern project structure, ORM optimization, class-based and function views, DRF integration, async views, caching strategies, and settings management for production-ready web applications. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: django, django best practices, django project structure, orm optimization, class-based views, drf, django rest framework, django async class-based views archetypes:
Senior Django engineer building production-ready web applications using Django 5.x patterns and modern Python practices. This skill covers the full stack — from project architecture and ORM optimization to API design with DRF, async views, caching strategies, and environment-aware settings management.
config/ (project settings) and apps/ (Django apps) directoriesdjango-split-settings or django-environselect_related() for ForeignKey lookups in loops, prefetch_related() for ManyToMany/ReverseForeignKey.count() + .all() with .exists() for boolean checks@method_decorator(login_required) or LoginRequiredMixin consistently on CBVsfastapi-development insteadargparse or click instead of Django's management commandsScaffold Project Structure — Create config/ for settings (local.py, production.py, base.py) and apps/ for each Django app. Use django-admin startproject config . to place project settings at the root level inside config/.
Configure Environment-Aware Settings — Load secrets from environment variables using os.environ or django-environ. Split settings into base.py, local.py, and production.py. Checkpoint: Verify that no hardcoded secrets appear in any settings file.
Create Apps with Proper Structure — Each app should contain: models.py, serializers.py, views.py, urls.py, services/ (business logic), signals.py, admin.py. Run python manage.py startapp <name> -t <template_path> if using custom templates.
Define Models with ORM Best Practices — Use explicit field types, add __str__ methods, define Meta ordering and indexes, use select_related/prefetch_related in queries. Checkpoint: Run python manage.py showmigrations to verify all migrations are accounted for before committing.
Build Views with Correct Abstraction — Use function-based views for simple CRUD, class-based views with mixins for complex logic with permissions and context customization. Keep business logic out of views.
Set Up DRF for API Endpoints — Create serializers, viewsets, configure pagination/throttling globally, define custom permission classes. Checkpoint: Test all API endpoints return correct status codes (200/201/400/403/404).
Optimize and Cache — Profile queries with django-debug-toolbar, add select_related/prefetch_related, configure Redis cache backend, add per-view and template fragment caching where appropriate.
Production Hardening — Enable HTTPS-only cookies, set SECURE_HSTS_SECONDS, configure logging to JSON format, set up sentry-like error tracking, review security headers via django-csp or similar.
Recommended layout for a production Django project with separate apps directory and environment-based settings:
myproject/
├── manage.py
├── config/ # Project-level settings
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py # django-split-settings loader
│ │ ├── base.py # Shared settings
│ │ ├── local.py # Development overrides
│ │ └── production.py # Production overrides
│ ├── urls.py # Root URLconf
│ ├── wsgi.py
│ └── asgi.py
├── apps/ # Django applications
│ ├── users/
│ │ ├── models.py
│ │ ├── serializers.py
│ │ ├── views.py
│ │ ├── urls.py
│ │ ├── services/
│ │ │ ├── __init__.py
│ │ │ └── user_service.py
│ │ └── apps.py
│ └── orders/
│ └── ...
├── static/
├── media/
├── requirements/
│ ├── base.txt
│ ├── local.txt
│ └── production.txt
└── .env.local # Never committed; loaded by django-environ
Settings loader (config/settings/__init__.py):
"""Environment-aware settings using django-split-settings."""
from pathlib import Path
from split_settings.tools import include, optional
BASE_DIR = Path(__file__).resolve().parent.parent.parent
include(
"base.py",
optional("env/local.env"),
optional("env/.secrets.yml"),
component="local.py", # Loaded only when ENV=local or not set
)
Base settings (config/settings/base.py):
"""Shared settings loaded by every environment."""
import os
from pathlib import Path
from environ import Env
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = Env()
Env.read_env(BASE_DIR / ".env.local") # Local overrides, not committed
SECRET_KEY = env("SECRET_KEY", default="change-me-in-production")
DEBUG = env.bool("DEBUG", default=False)
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[])
INSTALLED_APPS: list[str] = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# Third party
"rest_framework",
"corsheaders",
# Local apps
"apps.users",
"apps.orders",
]
DATABASES = {
"default": env.db("DATABASE_URL", default="sqlite:///db.sqlite3"),
}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# DRF defaults
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 25,
}
N+1 queries are the single most common Django performance issue. Use select_related (ForeignKey/OneToOne) and prefetch_related (ManyToMany/ReverseForeignKey) to reduce database round trips.
# apps/orders/models.py
from django.db import models
class Order(models.Model):
STATUS_CHOICES = [
("pending", "Pending"),
("confirmed", "Confirmed"),
("shipped", "Shipped"),
("delivered", "Delivered"),
("cancelled", "Cancelled"),
]
customer = models.ForeignKey(
"users.User",
on_delete=models.CASCADE,
related_name="orders",
)
status = models.CharField(max_length=16, choices=STATUS_CHOICES, default="pending")
total = models.DecimalField(max_digits=10, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["status", "-created_at"]),
models.Index(fields=["customer"]),
]
def __str__(self) -> str:
return f"Order #{self.pk} — {self.customer.get_full_name()}"
class OrderItem(models.Model):
order = models.ForeignKey(
Order,
on_delete=models.CASCADE,
related_name="items",
)
product_name = models.CharField(max_length=200)
quantity = models.PositiveIntegerField(default=1)
unit_price = models.DecimalField(max_digits=8, decimal_places=2)
@property
def subtotal(self) -> float:
return float(self.quantity * self.unit_price)
# ❌ BAD: N+1 query — one query per order to fetch customer, one per item
def list_orders_bad(user_id: int) -> list[dict]:
orders = Order.objects.filter(customer_id=user_id)
result = []
for order in orders:
# Each iteration fires a separate query for customer
result.append({
"id": order.pk,
"status": order.status,
"customer_email": order.customer.email, # N+1 on ForeignKey
"items_count": order.items.count(), # N+1 on reverse relation
"total": order.total,
})
return result
# ✅ GOOD: Prefetch all related data in a single query
def list_orders_good(user_id: int) -> list[dict]:
orders = Order.objects.filter(
customer_id=user_id
).select_related("customer").prefetch_related("items")
result = []
for order in orders:
result.append({
"id": order.pk,
"status": order.status,
"customer_email": order.customer.email, # No extra query
"items_count": order.items.count(), # Uses prefetched data
"total": order.total,
})
return orders
# ✅ GOOD: Use .exists() instead of .count() when you only need a boolean check
def has_pending_orders(customer_id: int) -> bool:
"""Check if customer has any pending orders — single query, no object loading."""
return Order.objects.filter(
customer_id=customer_id,
status="pending",
).exists()
# ✅ GOOD: Use .only() to fetch only needed fields (lightweight queryset)
def get_order_ids_and_totals() -> list[tuple[int, float]]:
"""Return only pk and total — skips loading created_at, status, customer_id, etc."""
return list(
Order.objects.only("pk", "total")
.values_list("pk", "total")
)
Key ORM optimization rules:
select_related does a SQL JOIN — use for ForeignKey and OneToOne relationshipsprefetch_related does a separate lookup in Python — use for ManyToMany and reverse ForeignKey.exists() is faster than .count() > 0 — Django translates it to SELECT (1) LIMIT 1.only() / .defer() control which columns are loaded from the database rowMeta.indexesUse CBVs when you need reusable logic across multiple views. Use function-based views for simple, one-off endpoints. Custom mixins let you compose behavior cleanly.
# apps/users/mixins.py
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.shortcuts import get_object_or_404
from rest_framework import generics
class OwnerOnlyMixin:
"""Restrict access to the owner of an object."""
def get_queryset(self):
qs = super().get_queryset()
# In function-based views, check ownership in the view directly
return qs.filter(owner=self.request.user)
class JSONResponseMixin:
"""Mix in JSON response generation for CBVs that need it alongside templates."""
def render_to_response(self, context, **response_kwargs):
import json
from django.http import JsonResponse
return JsonResponse(context)
# apps/users/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from .models import UserProfile
class UserProfileListView(LoginRequiredMixin, ListView):
"""List profiles for staff users only. Non-staff see only their own profile."""
model = UserProfile
template_name = "users/profile_list.html"
context_object_name = "profiles"
def get_queryset(self):
if self.request.user.is_staff:
return UserProfile.objects.select_related("user").all()
return UserProfile.objects.filter(user=self.request.user)
class UserProfileDetailView(LoginRequiredMixin, DetailView):
model = UserProfile
template_name = "users/profile_detail.html"
context_object_name = "profile"
def get_queryset(self):
# Ensure users can only view their own profile or staff sees all
qs = UserProfile.objects.select_related("user__auth_user")
if not self.request.user.is_staff:
return qs.filter(user=self.request.user)
return qs
class UserProfileCreateView(LoginRequiredMixin, CreateView):
model = UserProfile
fields = ["bio", "avatar"]
template_name = "users/profile_form.html"
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
def get_success_url(self) -> str:
return self.object.get_absolute_url()
When to choose function-based views instead:
# apps/orders/views.py
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from apps.orders.models import Order
@require_POST
def checkout_order(request) -> JsonResponse:
"""Simple POST-only view — no CBV needed for this single action."""
product_id = request.POST.get("product_id")
quantity = int(request.POST.get("quantity", 1))
if not product_id or quantity < 1:
return JsonResponse({"error": "Invalid parameters"}, status=400)
order = Order.objects.create(
customer=request.user,
total=0.00, # Simplified; in real code, calculate from cart items
)
return JsonResponse({"order_id": order.pk}, status=201)
# Use CBVs when you have complex logic that needs reuse:
# - Multiple HTTP methods with different permission requirements
# - Pagination + filtering + ordering all composed together
# - Multiple endpoints sharing the same queryset or serializer
DRF serializers handle validation, serialization, and deserialization. Use nested serializers for complex objects, custom validate_ methods for cross-field validation, and global pagination/throttling config.
# apps/orders/serializers.py
from decimal import Decimal
from rest_framework import serializers
from .models import Order, OrderItem
class OrderItemSerializer(serializers.ModelSerializer):
"""Nested serializer for order items within an order."""
subtotal = serializers.DecimalField(
max_digits=8,
decimal_places=2,
read_only=True,
)
class Meta:
model = OrderItem
fields = ["pk", "product_name", "quantity", "unit_price", "subtotal"]
extra_kwargs = {
"unit_price": {"min_value": Decimal("0.01")},
"quantity": {"min_value": 1},
}
class OrderCreateSerializer(serializers.ModelSerializer):
"""Handles order creation with nested items in a single request."""
items = OrderItemSerializer(many=True)
class Meta:
model = Order
fields = ["pk", "customer", "status", "total", "items", "created_at"]
read_only_fields = ["pk", "customer", "total", "created_at"]
def validate_items(self, items: list[dict]) -> list[dict]:
"""Ensure each item has positive quantity and valid pricing."""
if not items:
raise serializers.ValidationError("An order must have at least one item.")
for idx, item in enumerate(items):
qty = item.get("quantity", 0)
price = item.get("unit_price")
if qty < 1:
raise serializers.ValidationError(
f"Item {idx}: quantity must be >= 1"
)
if price is None or price <= 0:
raise serializers.ValidationError(
f"Item {idx}: unit_price must be positive"
)
return items
def create(self, validated_data: dict) -> Order:
"""Extract nested items and create order with calculated total."""
items_data = validated_data.pop("items")
order = Order.objects.create(
customer=self.context["request"].user,
**validated_data,
)
total = Decimal("0.00")
for item_data in items_data:
item = OrderItem.objects.create(order=order, **item_data)
total += item.subtotal
order.total = total
order.save(update_fields=["total"])
return order
def to_representation(self, instance: Order) -> dict:
"""Include nested items in read responses."""
rep = super().to_representation(instance)
rep["items"] = OrderItemSerializer(
instance.items, many=True
).data
return rep
# apps/orders/views.py — ViewSets with DRF best practices
from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import Order
class OrderViewSet(viewsets.ModelViewSet):
"""Full CRUD for orders with permission and pagination."""
serializer_class = OrderCreateSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(
customer=self.request.user
).select_related("customer").prefetch_related("items")
def get_serializer_class(self) -> type[serializers.ModelSerializer]:
"""Use detailed serializer for read, compact for write."""
if self.action == "list":
return OrderCreateSerializer
return super().get_serializer_class()
def perform_create(self, serializer):
"""Attach current user to the order on creation."""
serializer.save(customer=self.request.user)
DRF global configuration (config/settings/base.py):
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 25,
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/day",
"upload": "5/minute",
},
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
"rest_framework.parsers.FormParser",
"rest_framework.parsers.MultiPartParser",
],
}
Django 4.1+ supports native async views. Use async for I/O-bound operations (external API calls, database queries with async databases). Avoid async for CPU-bound work or blocking operations.
# apps/orders/async_views.py
import asyncio
from typing import Any
from asgiref.sync import sync_to_async
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from .models import Order
@require_http_methods(["GET", "POST"])
async def order_stats_endpoint(
request: HttpRequest,
) -> HttpResponse:
"""Async view that fetches order statistics concurrently."""
async def get_pending_count() -> int:
return await Order.objects.filter(status="pending").aaggregate_total_count() # noqa: E501
async def get_revenue_today() -> float:
from django.utils import timezone
today = timezone.localdate()
result = await Order.objects.filter(
created_at__date=today,
status__in=["confirmed", "shipped", "delivered"],
).aggregate(total=models.Sum("total"))
return float(result["total"] or 0)
# Run both queries concurrently
pending, revenue = await asyncio.gather(
get_pending_count(),
get_revenue_today(),
)
return JsonResponse({
"pending_orders": pending,
"revenue_today": round(revenue, 2),
})
# ✅ Use sync_to_async when bridging synchronous code into async views
@method_decorator(require_http_methods(["GET"]), name="dispatch")
class OrderStatsView:
"""Class-based async view using sync-to-async bridge for ORM."""
async def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
# All ORM calls must use .afilter(), .aget(), etc. with native async DB backend
orders_count = await Order.objects.acount()
return JsonResponse({"total_orders": orders_count})
# ❌ BAD: Mixing blocking I/O inside an async view — this blocks the entire event loop
async def bad_async_view(request: HttpRequest) -> HttpResponse:
import time
time.sleep(5) # Blocks the event loop for 5 seconds!
orders = await Order.objects.acount()
return JsonResponse({"count": orders})
# ❌ BAD: Calling a synchronous ORM method in an async view — raises RuntimeWarning
async def also_bad_async_view(request: HttpRequest) -> HttpResponse:
from django.db import connection
# This is a sync call inside async context — will cause issues with async DB backends
orders = Order.objects.all() # Should use .all().aiterator() or asyncio.gather patterns
return JsonResponse({"count": len(list(orders))})
When NOT to use async in Django:
psycopg2 is sync-only)requests)Use Redis or Memcached as cache backend in production. Use database-backed cache only for development. Combine per-view caching, template fragment caching, and manual cache invalidation for maximum performance.
# config/settings/base.py — Cache configuration
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": env("REDIS_URL", default="redis://127.0.0.1:6379/1"),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"SOCKET_CONNECT_TIMEOUT": 5,
"SOCKET_TIMEOUT": 5,
"CONNECTION_POOL_KWARGS": {"max_connections": 50},
},
},
}
# For development only — falls back to memory cache if Redis not available
if DEBUG:
CACHES["default"] = {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
}
# apps/orders/views.py — Per-view caching with varying cache keys
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
from django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 minutes
@method_decorator(vary_on_cookie, name="dispatch") # Vary by session cookie
class PublicOrderListView:
"""Cached public order listing — different cache per logged-in user."""
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
# In a real app, use ListView with @method_decorator or function-based @cache_page
# apps/products/serializers.py — Manual cache invalidation pattern
from django.core.cache import cache
from .models import Product
def invalidate_product_cache(product_id: int) -> None:
"""Invalidate cached product data when the product changes.
Uses a key prefix to namespace cache keys by entity type,
making bulk invalidation simple and safe.
"""
cache.delete_pattern(f"product:{product_id}:*")
def get_or_cache_product(product_id: int) -> Product | None:
"""Fetch with cache — write-through on miss."""
cache_key = f"product:{product_id}"
product = cache.get(cache_key)
if product is None:
from .models import Product as ProductModel
product = ProductModel.objects.select_related(
"category", "brand"
).filter(pk=product_id).first()
if product:
# Cache for 1 hour with a structured key
cache.set(cache_key, product, timeout=3600)
return product
# apps/products/signals.py — Auto-invalidate on model save
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
@receiver(post_save, sender="products.Product")
@receiver(post_delete, sender="products.Product")
def invalidate_on_product_change(sender, instance, **kwargs):
"""Automatically invalidate cached product data on any model mutation."""
invalidate_product_cache(instance.pk)
Template fragment caching:
{# templates/products/product_list.html #}
{% load cache %}
{# Cache entire section for 10 minutes, keyed by user's language and page number #}
{% cache 600 product_list request.LANGUAGE_CODE request.GET.get "page" "1" %}
<div class="product-grid">
{% for product in products %}
<article class="product-card">
<h3>{{ product.name }}</h3>
<p class="price">${{ product.price }}</p>
</article>
{% empty %}
<p>No products available.</p>
{% endfor %}
</div>
{% endcache %}
Production settings must never contain secrets. Use django-environ to load from environment variables and .env files that are git-ignored. Split settings into base/local/production layers.
# config/settings/base.py — Core shared configuration
import os
from pathlib import Path
from environ import Env
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = Env()
Env.read_env(BASE_DIR / ".env.local", overwrite=True) # Local overrides, not committed to VCS
# SECURITY — never hardcode these
SECRET_KEY: str = env("DJANGO_SECRET_KEY")
DEBUG: bool = env.bool("DJANGO_DEBUG", default=False)
ALLOWED_HOSTS: list[str] = env.list(
"DJANGO_ALLOWED_HOSTS",
default=["localhost", "127.0.0.1"],
)
# CORS — only allow known origins in production
CORS_ALLOWED_ORIGINS: list[str] = env.list(
"DJANGO_CORS_ORIGINS",
default=[],
)
# Email configuration — different per environment
EMAIL_BACKEND = env(
"DJANGO_EMAIL_BACKEND",
default="django.core.mail.backends.console.EmailBackend", # Dev only
)
EMAIL_HOST = env("DJANGO_EMAIL_HOST", default="localhost")
EMAIL_PORT = env.int("DJANGO_EMAIL_PORT", default=587)
EMAIL_USE_TLS = env.bool("DJANGO_EMAIL_USE_TLS", default=True)
DEFAULT_FROM_EMAIL = env(
"DJANGO_DEFAULT_FROM_EMAIL",
default="noreply@example.com",
)
# config/settings/local.py — Development overrides
DEBUG = True
ALLOWED_HOSTS = ["*", "localhost", "127.0.0.1"]
# django-debug-toolbar for query inspection (only in local)
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
INTERNAL_IPS = ["127.0.0.1"]
LOGGING: dict = {
"version": 1,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"root": {
"handlers": ["console"],
"level": "DEBUG",
},
}
# config/settings/production.py — Production overrides
import logging
# Security hardening for production
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Logging — structured JSON in production for log aggregation
LOGGING: dict = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"fmt": "%(asctime)s %(name)s %(levelname)s %(message)s",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"root": {
"handlers": ["console"],
"level": "WARNING",
},
"loggers": {
"django.security.DisallowedHost": {
"handlers": ["console"],
"level": "ERROR",
"propagate": False,
},
},
}
# Rate limiting — stricter in production
REST_FRAMEWORK = {
**{
k: v for k, v in {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {"user": "5000/day"},
}.items()
},
}
# Database — use PostgreSQL connection pooling in production
DATABASES = {
"default": env.db(
"DATABASE_URL",
default="postgres://user:password@localhost:5432/dbname", # Never commit!
),
}
.env.local template (git-ignored, shown for reference):
# .env.local — NEVER commit this file
DJANGO_SECRET_KEY=your-ultra-secret-key-change-in-production
DJANGO_DEBUG=True
DATABASE_URL=postgres://user:password@localhost:5432/myproject_dev
REDIS_URL=redis://127.0.0.1:6379/1
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
.gitignore entry:
# Django environment files — contain secrets
.env
.env.local
.env.*.local
*.secrets.yml
select_related() for ForeignKey/OneToOne lookups inside loops to avoid N+1 queriesprefetch_related() for ManyToMany and reverse ForeignKey relationshipsdjango-environ, os.environ) — never hardcode themget_or_create() with explicit defaults dict to avoid race conditions on unique fields.exists() instead of .count() > 0 for boolean database checks — it's a single queryMeta.indexes on models that are frequently filtered or sorted by specific fields__str__ methods to all models for admin display and debug outputmax_length, choices, default, and null=True/blank=False as appropriate.count() then .all() — use .exists() or .iterator() instead* imports in Django apps (from module import *)related_name on ForeignKey fields — it causes reverse accessor conflictsconnection.cursor() with %s parameters.env, .env.local, or any file containing secrets to version controlsuper().get_queryset() to preserve filtering chainssync_to_async around blocking operations like time.sleep() or requests.get()update_fields in save() calls when updating only specific fields — it prevents race conditionsWhen implementing or reviewing Django code, produce:
Meta options, indexes, and __str__ methodselect_related/prefetch_related usage where applicableto_representation override| Skill | Purpose |
|---|---|
fastapi-development | For lightweight microservices where Django is overkill — same Python ecosystem, async-native |
Authoritative documentation links for Django development. The model follows markdown links at load time to resolve external references and inline content.