BugMojoBugMojoBugMojo
FeaturesPricingBlogGuidesAbout
Log inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Get started
  • Log in

Resources

  • Blog
  • Guides
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Privacy
  • Sitemap
  • Engineering
  • Playbooks
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Bug tracking by framework
  4. Django 5.2
Bug tracking by framework

Bug tracking for Django — session replay, console + HAR (2026)

Django bug-reporting guide for QA + dev teams: framework-specific gotchas, common bugs, and how to capture them with BugMojo session replay.

7 min read·Python
Isometric thin line-art browser rendering a Django page with an htmx partial-swap region, a 403 CSRF request card peeling off toward an IDE node, lit by a single lime glow

What Django teams ship with BugMojo

Django answers an unsafe request with HTTP 403 Forbidden the moment CsrfViewMiddleware rejects it, and on an htmx app that 403 is easy to misread. A server-rendered form carries the token through the {% csrf_token %} tag, which injects a per-response masked value that also defends against the BREACH attack; an AJAX or htmx POST to the same view instead has to send the token in the X-CSRFToken header (configurable via CSRF_HEADER_NAME). The django-htmx docs recommend setting it once so every element inherits it: <body hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'> — and note that is {{ csrf_token }} the variable, not the {% csrf_token %} hidden-input tag. For HTTPS requests with no Origin header Django adds strict Referer checking on top, so a proxy that rewrites those headers can produce a 403 that looks like nothing the user did. The failing request's headers and cookies settle it instantly; a screenshot cannot.

The other Django bug that quietly eats response time is the N+1 query. QuerySets are lazy and follow relations on attribute access, so a list view that renders one related object per row fires one SELECT for the list plus one more per row. The fix depends on the relation direction: select_related() emits a SQL JOIN for forward foreign-key and one-to-one fields, while prefetch_related() runs a second query and joins it in Python for reverse foreign-key and many-to-many fields. Django Debug Toolbar's SQL panel is the canonical way to see it — it groups repeats into Similar (same SQL, different parameters) and Duplicate (byte-identical) badges, so an N+1 shows as a high count of the same SELECT. This guide is for Django teams shipping server-rendered HTML and htmx partials in 2026: htmx sends HX-Request: true on every request it issues (how django-htmx's request.htmx tells a partial swap from a full navigation), and that header behavior is exactly what a captured network HAR makes legible when a partial 403s, 500s, or runs slow.

Django gotchas

Framework-specific failure modes engineers actually ship through. Each is hard to spot in a screenshot and obvious in a session replay.

A 403 Forbidden is a CSRF rejection, not a permissions error

CsrfViewMiddleware returns 403 when an unsafe request (POST/PUT/PATCH/DELETE) arrives without a valid csrfmiddlewaretoken or X-CSRFToken header. Intermittent 403s usually mean the CSRF/session cookie expired on an idle page, a CDN served a page with a stale masked token, CSRF_COOKIE_DOMAIN/SameSite is mis-scoped, or strict Referer checking failed on HTTPS behind a proxy. The request headers tell you which.

htmx needs the token in a header, and {{ csrf_token }} is not {% csrf_token %}

For htmx POSTs Django reads the token from the X-CSRFToken header, so set it once on the body: <body hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'>. Use the {{ csrf_token }} variable (a raw value), not the {% csrf_token %} tag (a hidden input). Getting the two confused sends an empty header and every mutating request 403s.

Swapping the region that holds the token removes it from the DOM

If an htmx swap or hx-boost replaces a region containing the token element, the token can vanish from the DOM after the first request and every following POST 403s. Set the token on a stable parent like <body>, or attach it in a htmx:configRequest listener that sets event.detail.headers['X-CSRFToken']. The symptom is the second submit failing while the first worked.

N+1 queries hide behind lazy QuerySets

QuerySets evaluate only when accessed and follow relations on attribute lookup, so a template that reads obj.related per row silently fires one query per row. Use select_related() (a JOIN) for forward FK/one-to-one and prefetch_related() (a second query joined in Python) for reverse FK/many-to-many. Inspect django.db.connection.queries to confirm the count dropped after the fix.

A 500 in an htmx partial gets swapped in silently

An htmx request usually returns a template fragment, and an unhandled exception becomes a 500 that gets swapped into a DOM region instead of replacing the page. With DEBUG=False the user sees a blank panel or stale content, not Django's error page, so the real status is invisible on screen. The 500 status and body live in the network response; the swap target is obvious in a DOM replay.

request.htmx depends on the HX-Request header arriving intact

django-htmx's request.htmx is true only because htmx sends HX-Request: true on every request it issues; that flag is how a view decides to return a fragment instead of a full page. If a proxy, fetch polyfill, or a hand-rolled request drops the header, the view returns a whole page into a partial slot and the layout breaks. Capturing the request headers shows whether HX-Request was actually present.

Common Django bugs

Real Django bug patterns — the symptom you will see in a report and the fix that actually works.

Every htmx POST returns 403 CSRF verification failed

Symptom: Server-rendered forms submit fine, but any htmx-issued POST/PUT/PATCH/DELETE returns 403 and the swap shows Django's CSRF failure page or a blank region.

Fix: htmx requests do not carry the hidden csrfmiddlewaretoken, so Django needs the token in the X-CSRFToken header. Set it once on the body with hx-headers using the {{ csrf_token }} variable, and avoid swapping the element that holds it. The captured request headers show whether X-CSRFToken was present, empty, or stale.

htmlhtml
<!-- set the token once so every htmx request inherits it -->
<body hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'>
  <!-- ... -->
</body>

<!-- or, in JS, attach it per request -->
<script>
  document.body.addEventListener('htmx:configRequest', (e) => {
    e.detail.headers['X-CSRFToken'] = getCookie('csrftoken');
  });
</script>

A list view fires one query per row (N+1)

Symptom: A page that renders a list with a related object per row is slow under real data; Django Debug Toolbar's SQL panel shows a high query count with Similar/Duplicate badges on the same SELECT.

Fix: Add select_related() for forward foreign-key and one-to-one relations (a single JOIN) or prefetch_related() for reverse foreign-key and many-to-many relations (a second query joined in Python). Confirm the drop by reading len(django.db.connection.queries) before and after.

pythonpython
# bug: one extra query per book to fetch book.author
for book in Book.objects.all():
    print(book.author.name)

# fix: forward FK -> JOIN in one query
for book in Book.objects.select_related('author'):
    print(book.author.name)

# reverse FK / M2M -> prefetch_related
authors = Author.objects.prefetch_related('books')

htmx partial 500s in production but works in dev

Symptom: A fragment view renders locally with DEBUG=True but returns a 500 in production; the user sees a blank or stale region instead of an error, because the failing response was swapped into the DOM.

Fix: The usual causes are a template variable that is None only in prod data, a missing template or context key for the partial, or a database error on rows that only exist in production. Because the 500 is swapped in, read the network response for the status and body, then use the DOM replay plus console to see which swap target received it.

pythonpython
# return a fragment for htmx, full page otherwise (django-htmx)
def rows(request):
    qs = Item.objects.select_related('owner')
    template = 'items/_rows.html' if request.htmx else 'items/list.html'
    return render(request, template, {'items': qs})

BugMojo vs alternatives

The honest comparison — where BugMojo wins, and where another tool might serve you better.

FeatureBugMojoDebug Toolbar / Sentry
DOM session replay of the user's exact steps✅ rrweb-based⚠️ Sentry replay (sampled); Debug Toolbar none
Network HAR with the 403/500 request + X-CSRFToken/HX-Request headers✅ shows the failing request and its headers⚠️ server-side view only
Captures the bug from a tester's or user's real browser✅ browser-layer, no SDK❌ Debug Toolbar is local-dev only
MCP server: Claude Code / Cursor read the report in the IDE✅ bug is a first-class assignee❌ none
Always-on production error monitoring & aggregation❌ on-demand capture✅ Sentry is more complete
Local SQL / query-count / template timing inspection❌✅ Django Debug Toolbar
Console + network (HAR) capture✓Partial
Zero-setup Quick CaptureNo project, no SDKAccount / SDK required
The BugMojo column is highlighted. The closing rows are BugMojo’s core wedge: rrweb session replay, MCP for AI agents, console + network capture, and zero-setup Quick Capture.
Capture your next bug in 15 seconds

BugMojo records the DOM, console, and network — then ships a one-click ticket with the full replay attached. No SDK, no setup.

Try BugMojo free

Frequently asked questions

Frequently asked questions

Sources

  1. Django 5.2 — Cross Site Request Forgery protection (403 on failure, {% csrf_token %}, strict Referer checking, CSRF_COOKIE_* settings) — Django Software Foundation (official docs) (2025)
  2. Django 5.2 — How to use Django's CSRF protection (X-CSRFToken header, CSRF_HEADER_NAME, getCookie('csrftoken')) — Django Software Foundation (official docs) (2025)
  3. Django 5.2 — Database access optimization (select_related/prefetch_related for N+1, connection.queries, lazy QuerySets) — Django Software Foundation (official docs) (2025)
  4. django-htmx — Tips (recommended <body hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'> pattern; request.htmx middleware) — django-htmx (Adam Johnson, official docs) (2025)
  5. Django 5.2 released — LTS, April 2, 2025; composite primary keys; security support to April 2028 — Django Software Foundation (weblog) (2025-04-02)
  6. Django Developer Survey 2024 results (ASGI 37%, async views 14%, DRF 49% favorite package) — JetBrains + Django Software Foundation (2024)
Share:

More frameworks

Pick another — each guide has its own gotchas, comparison, and fixes.

React 19
JavaScript
Vue 3.5
JavaScript
Laravel 12
PHP
Next.js 15
JavaScript
WordPress
PHP / CMS
Angular 19
TypeScript

On this page

  • What Django teams ship with BugMojo
  • Django gotchas
  • Common Django bugs
  • BugMojo vs alternatives