AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Mir Backend Python Django

skill-anantbhandarkar-make-it-right-mir-backend-python-django · by anantbhandarkar

Make It Right (Django module). Django 5 + Django REST Framework specific reliability augmentation. Use alongside the mir-backend skill when the target stack is Django — it carries the mechanical footguns that the framework-agnostic skill deliberately omits: ORM N+1 with select_related/prefetch_related, queryset laziness and caching semantics, migration safety on populated tables (NOT NULL / index…

— No reviews yet
0 installs
31 views
0.0% view→install

Install

$ agentstack add skill-anantbhandarkar-make-it-right-mir-backend-python-django

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-anantbhandarkar-make-it-right-mir-backend-python-django)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Mir Backend Python Django? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

/mir-backend-python-django · Make It Right (Django)

Bottom tier of the chain: mir-backend (generic gates) → mir-backend-python (CPython runtime model) → this (Django/DRF library mechanics). Run the gates first; load the Python runtime tier for the concurrency/process model; reach for this at Gate 5 (design mechanics), Gate 6 (implementation), and Gate 7 review. Runtime-level concerns (GIL, async-vs-sync, blocking the event loop, fork-safe pools, cold start) live in mir-backend-python — not here.

Stack assumed: Django 5 · Django REST Framework · PostgreSQL (psycopg3 or psycopg2) · Celery (for async work). If the project uses a different DB adapter or a pure-Django (no DRF) API surface, note the divergence before applying these.

The Django footguns AI walks into most

These are the stack-specific cousins of the failure-mode catalog. Each is something Django/DRF code gets wrong even when the logic is right.

1. ORM N+1 — the silent query multiplier

Accessing a related object inside a loop fires one query per iteration because the ORM resolves relations lazily by default. AI writes for order in orders: print(order.user.email) and ships an N+1 without noticing — the Django ORM makes it invisible until a profiler surfaces it.

selectrelated vs prefetchrelated:

  • select_related(*fields) — for FK and one-to-one relations; performs a SQL JOIN, one query total. Use for "to-one" traversal depth you already know.
  • prefetch_related(*fields) — for many-to-many and reverse FK (one-to-many) relations; runs a separate IN query and stitches in Python. Use for ManyToManyField, related_name reverse accessors, or deeply nested paths.
  • Combine both: queryset.select_related("user").prefetch_related("tags").

Bound columns: only("id", "email") loads a sparse model (accessing un-fetched fields triggers a lazy query); defer("body") is the inverse. values("id", "email") returns dicts — no model overhead, no lazy traps, best for read-only serialization.

# WRONG — N+1
orders = Order.objects.filter(status="pending")
for order in orders:
    send_email(order.user.email)   # query per iteration

# RIGHT
orders = Order.objects.filter(status="pending").select_related("user")
for order in orders:
    send_email(order.user.email)   # no extra queries

Detection: django-debug-toolbar (dev), nplusone (test-time, raises on N+1 unless explicitly allowed), django.db.connection.queries in tests with assertNumQueries.

2. QuerySet laziness and caching — evaluation is deferred and fragile

A queryset is lazy: it hits the DB only on iteration, list(), len(), slicing, or explicit evaluation. Once evaluated, the result is cached on the queryset object — but re-evaluating the queryset in a new expression re-hits the DB.

AI mistakes:

  • Truthiness test on a queryset: if qs: evaluates the full queryset and caches. Use .exists() instead — single SELECT 1 with a LIMIT 1. Similarly, use .count() over len(qs) when you don't need the objects.
  • Storing a queryset and iterating twice: the second iteration replays from cache only if the queryset was already evaluated and the object is the same instance. If you slice (qs[:10]) before evaluating, the slice returns a new queryset — the cache is gone.
  • Slicing hits the DB: qs[5:10] issues LIMIT 5 OFFSET 5. It does not re-use a prior full evaluation.
  • Queryset in a list vs queryset reference: convert to list(qs) when you need repeated in-Python iteration; keep the queryset reference if you want to chain filters later.
# WRONG — two DB hits
qs = MyModel.objects.filter(active=True)
if qs:                # hit 1: full SELECT
    for obj in qs:   # hit 2: full SELECT again (new expression context)
        ...

# RIGHT
qs = list(MyModel.objects.filter(active=True))   # one hit, cached as a list
if qs:
    for obj in qs:   # no extra queries
        ...

# OR for existence only
if MyModel.objects.filter(active=True).exists():  # SELECT 1 LIMIT 1
    ...

3. Migrations on populated tables — the big one

AI writes migrations as if the table is empty. On a table with millions of rows, naive schema changes lock the table and block production traffic.

NOT NULL column with a default — the rewrite lock trap: Adding NOT NULL with default=... in a single migration causes Postgres (. Always enumerate fields = ["name", "email"] or use exclude` defensively.

DRF serializer: same trap with fields = "__all__". Enumerate fields explicitly. Mark fields the client must not write as read_only=True in read_only_fields or as serializers.ReadOnlyField():

class OrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = ["id", "status", "total", "user_id"]
        read_only_fields = ["id", "user_id"]   # never user-supplied

Response leakage: response_model (FastAPI) has no Django equivalent by default — serializer controls what leaves. Audit every to_representation override and every nested serializer for fields that expose internal state (password hashes, internal flags, other users' data).

Object-level authorization: DRF has_object_permission in a BasePermission subclass is distinct from has_permission. AI implements authentication and forgets object-level ownership checks → IDOR. Always call self.check_object_permissions(request, obj) in get_object(), or inherit from GenericAPIView which does it automatically.

6. Async views (Django 4.1+ async ORM) — sync ORM in an async view raises

Django 4.1+ supports async def views and async ORM methods. The pitfall: calling the synchronous ORM API (Model.objects.filter(...).first()) inside an async def view raises SynchronousOnlyOperation — Django detects the event loop and refuses to block it.

Use the async ORM counterparts:

# WRONG — raises SynchronousOnlyOperation in an async view
async def my_view(request):
    obj = MyModel.objects.get(pk=1)   # sync — blocks the loop

# RIGHT
async def my_view(request):
    obj = await MyModel.objects.aget(pk=1)
    # or
    obj = await sync_to_async(MyModel.objects.get)(pk=1)  # escape hatch for legacy code

Async ORM methods: aget(), acreate(), asave(), adelete(), aupdate(), aiterator(), aexists(), acount(). Most ORM queryset evaluations have an a-prefixed counterpart in Django 4.1+.

Deployment note: the runtime-level choice (ASGI server — Daphne, Uvicorn — vs WSGI — gunicorn) lives in mir-backend-python. Most Django projects still run on WSGI workers; async def views only get true async execution under ASGI. Under WSGI, Django runs async views in a synchronous adapter — you get correctness but no I/O concurrency.

7. Signals — implicit side effects and traceability

Django signals (post_save, pre_delete, etc.) register callbacks that fire implicitly whenever a model is saved or deleted, anywhere in the codebase. AI adds signals to hook in business logic because it feels clean, then that logic becomes invisible to the reader of the view or serializer.

Problems:

  • Implicit execution: a post_save on Order fires inside any save() call — including tests, management commands, data migrations, and shell operations. Side effects in those contexts are almost always wrong.
  • Transaction coupling: a signal fires inside the open transaction of the triggering save(). Sending an email or enqueueing a task in a post_save signal is the same anti-pattern as doing it inside transaction.atomic() — use transaction.on_commit() inside the signal handler if the side effect must survive a rollback.
  • Hard to trace: the causal chain from order.save() to "email was sent" is invisible unless you know to search for signal receivers.

Prefer explicit calls: a service function that saves the model and then calls the downstream logic directly. Reserve signals for genuine cross-cutting concerns (audit logging, cache invalidation) where the explicitness would require modifying every caller. Keep signal handlers idempotent.

How this slots into the pipeline

  • Gate 5 (Design): state ORM access patterns (selectrelated/prefetchrelated), transaction boundaries (atomic, on_commit), and the serializer field lists. A migration plan for populated tables must name the three-step pattern above.
  • Gate 6 (Implementation): code against the footguns above; run assertNumQueries in tests for any queryset-heavy path; confirm on_commit wraps all post-save side effects.
  • Gate 7 (Review): the reliability-reviewer checks items 1–7 here. The migration-reviewer checks each new migration for NOT NULL columns, missing CONCURRENTLY, and missing batched backfills.

Edit boundary (what belongs here vs. the runtime tier)

This module holds ONLY Django/DRF library mechanics. Apply the 3-tier placement test before adding anything:

  • True for Go/Node/Java too (idempotency, invariants, gates, risk register, observability)? → generic core (mir-backend).
  • True for every Python framework on CPython (GIL, async-vs-sync, blocking the event loop, fork-safe pools, cold start, asyncio task hygiene)? → runtime tier (mir-backend-python). Note: the async/ASGI deployment decision lives there; here we only cover the SynchronousOnlyOperation Django-specific trap.
  • A mechanical footgun of this library (ORM N+1, queryset laziness, migration locking, on_commit, mass assignment, signal coupling)? → here.
  • A different framework on Python (FastAPI, Flask) → its own mir-backend-python- module. A different runtime → its own tier. Never widen this one.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.