Django Knowledge Patch (5.2 – 6.0)
Claude knows Django through 5.1. This skill covers Django 5.2 LTS (April 2025) and Django 6.0 (December 2025).
Index
| Topic | Reference | Key features |
|-------|-----------|--------------|
| Models & ORM | references/models-and-orm.md | Composite PKs, StringAgg cross-db, AnyValue, auto-refresh expressions, NotUpdated |
| Templates | references/templates.md | Partials (#partial_name), simple_block_tag, forloop.length |
| Content Security Policy | references/csp.md | Built-in CSP middleware, nonce support, per-view overrides |
| Background Tasks | references/background-tasks.md | @task decorator, enqueue/result API, built-in backends |
| Views & HTTP | references/views-and-http.md | reverse() query/fragment, preserve_request, AsyncPaginator, content negotiation |
| Migration Guide | references/migration-guide.md | Breaking changes, deprecations, version requirements |
Quick Reference
Composite Primary Keys (5.2)
class OrderLineItem(models.Model):
pk = models.CompositePrimaryKey("product_id", "order_id")
product = models.ForeignKey(Product, on_delete=models.CASCADE)
order = models.ForeignKey(Order, on_delete=models.CASCADE)
item.pk returns a tuple. Filter with filter(pk=(1, "A755H")). Cannot use ForeignKey to target composite PK models — use ForeignObject. Not supported in admin. See references/models-and-orm.md.
Template Partials (6.0)
{% partialdef view_count inline %}
<span id="view-count">{{ video.views }}</span>
{% endpartialdef %}
Render in isolation: render(request, "video.html#view_count", context) — ideal for htmx. inline renders in place; without it, definition is silent. See references/templates.md.
Content Security Policy (6.0)
# settings.py
from django.utils.csp import CSP
MIDDLEWARE = ["django.middleware.csp.ContentSecurityPolicyMiddleware", ...]
TEMPLATES = [{"OPTIONS": {"context_processors": ["django.template.context_processors.csp", ...]}}]
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, CSP.NONCE],
"style-src": [CSP.SELF, CSP.NONCE],
"img-src": [CSP.SELF, "https:"],
}
# Report-only mode (non-blocking)
SECURE_CSP_REPORT_ONLY = {
"default-src": [CSP.SELF],
"report-uri": "/csp-report/",
}
Use {{ csp_nonce }} in templates for inline scripts/styles. Per-view: @csp_override({...}) or @csp_override({}) to disable. Constants: CSP.SELF, CSP.NONE, CSP.NONCE, CSP.UNSAFE_INLINE, CSP.UNSAFE_EVAL, CSP.STRICT_DYNAMIC. See references/csp.md.
Background Tasks (6.0)
from django.tasks import task
@task
def send_email(user_id): ...
result = send_email.enqueue(user_id=42)
result.status # READY / RUNNING / SUCCESSFUL / FAILED
Configure in settings:
TASKS = {
"default": {
"BACKEND": "django.tasks.backends.immediate.ImmediateBackend", # dev/test only
}
}
Built-in backends are dev/test only (ImmediateBackend, DummyBackend). Production needs third-party backend. Decorator options: @task(priority=2, queue_name="emails", backend="default"). Modify before enqueue: send_email.using(priority=10, run_after=timedelta(minutes=5)).enqueue(...). All args must be JSON-serializable. See references/background-tasks.md.
reverse() with Query & Fragment (5.2)
reverse("search", query={"q": "django", "page": "2"}) # "/search/?q=django&page=2"
reverse("docs", fragment="section-3") # "/docs/#section-3"
preserve_request on Redirects (5.2)
redirect("/new/", preserve_request=True) # 307 instead of 302
redirect("/new/", permanent=True, preserve_request=True) # 308 instead of 301
New Aggregates (6.0)
| Aggregate | Import | Usage |
|-----------|--------|-------|
| StringAgg | django.db.models.aggregates | Cross-db. Breaking: delimiter requires Value() |
| AnyValue | django.db.models | Arbitrary non-null value from group (PG 16+, SQLite, MySQL, Oracle) |
Key Changes at a Glance
| Change | Version | Impact |
|--------|---------|--------|
| DEFAULT_AUTO_FIELD → BigAutoField | 6.0 | Remove explicit setting if already set |
| Python 3.10–3.11 dropped | 6.0 | Minimum Python 3.12 |
| as_sql() must return tuple params | 6.0 | Custom expressions need update |
| Field.pre_save() called multiple times | 6.0 | Must be idempotent |
| MySQL default charset → utf8mb4 | 5.2 | Automatic, may affect column sizes |
| MariaDB 10.5 dropped | 6.0 | Minimum MariaDB 10.6 |
Content Negotiation & Response Helpers (5.2)
# Content negotiation
def my_view(request):
preferred = request.get_preferred_type(["text/html", "application/json"])
if preferred == "application/json":
return JsonResponse(data)
return render(request, "page.html", context)
# String access to response (cached, charset-aware)
response = self.client.get("/")
response.text # str, instead of response.content.decode()
simple_block_tag (5.2)
@register.simple_block_tag
def my_card(nodelist, title):
return f'<div class="card"><h2>{title}</h2>{nodelist}</div>'
{% my_card title="Hello" %}
<p>Card content here</p>
{% endmy_card %}
AsyncPaginator (6.0)
from django.core.paginator import AsyncPaginator
paginator = AsyncPaginator(queryset, per_page=25)
page = await paginator.aget_page(page_number)
Auto-refresh Expression Fields (6.0)
GeneratedField values and fields assigned expressions (e.g., obj.updated_at = Now()) are automatically refreshed from the database after save(). No more manual refresh_from_db(). On backends with RETURNING (SQLite, PostgreSQL, Oracle): single query. On MySQL/MariaDB: fields are marked deferred.
Model.NotUpdated Exception (6.0)
Model.save() raises Model.NotUpdated (not generic DatabaseError) when a forced update (force_update=True / update_fields) affects zero rows.
Other New Features
forloop.length(6.0): Total item count in{% for %}loops —{{ forloop.length }}.JSONArray(5.2): Database function returning JSON array from expressions.- New form widgets (5.2):
ColorInput,SearchInput,TelInput. - Shell auto-imports (6.0):
settings,connection,models,functions,timezone. QuerySet.values()/values_list()ordering (5.2): SELECT matches specified expression order (was unpredictable — affects.union()).- Oracle connection pools (5.2): Via
OPTIONS["pool"]inDATABASES. startproject/startapp(6.0): Now creates target directory if it doesn't exist.{% querystring %}(6.0): Now always prefixes output with?, accepts multiple positional mapping args.
Full deprecation list in references/migration-guide.md.