Agent Skills: Django Knowledge Patch (5.2 – 6.0)

Django changes since training cutoff (latest: 6.0) — composite primary keys, CSP middleware, template partials, background tasks, async paginator. Load before working with Django.

UncategorizedID: nevaberry/nevaberry-plugins/django-knowledge-patch

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Nevaberry/nevaberry-plugins/tree/HEAD/plugins/django-knowledge-patch/skills/django-knowledge-patch

Skill Files

Browse the full folder contents for django-knowledge-patch.

Download Skill

Loading file tree…

plugins/django-knowledge-patch/skills/django-knowledge-patch/SKILL.md

Skill Metadata

Name
django-knowledge-patch
Description
"Django changes since training cutoff (latest: 6.0) — composite primary keys, CSP middleware, template partials, background tasks, async paginator. Load before working with Django."

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_FIELDBigAutoField | 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"] in DATABASES.
  • 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.