A fast Django/DRF linter with live model introspection. Built on Thorn.
Catches bugs, security issues, and performance problems by combining static AST analysis with runtime model graph validation.
Standard Python linters don't understand Django. They can't tell you that your serializer references a field that doesn't exist, that your .filter() uses a nonexistent lookup, or that your select_related() follows every FK chain in your database.
thorn-django can, because it reads your actual model graph at runtime and cross-references it against your code.
docker run --rm -v "$PWD:/src" ghcr.io/pescheckit/thorn-django:latest .Download from Releases, or build from source:
cargo install --git https://github.com/pescheckit/thorn-django# Static checks only (no setup required)
thorn-django .
# With Django model introspection
thorn-django . --django-settings=myproject.settings
# Pre-generate the graph (works in Docker)
python -m thorn_django --settings myproject.settings
thorn-django .| Code | Issue |
|---|---|
| DJ001 | null=True on string fields, use blank=True |
| DJ002 | exclude in ModelForm/Serializer Meta, use fields |
| DJ003 | .raw() or .extra(), prefer QuerySet methods |
| DJ006 | ForeignKey without on_delete |
| DJ007 | fields = '__all__', new fields auto-exposed |
| DJ008 | order_by('?'), full table scan |
| DJ009 | QuerySet in boolean context, use .exists() |
| DJ011 | self.field += N race condition, use F() |
| DJ014 | SQL injection via string interpolation in .raw()/.execute() |
| DJ017 | @csrf_exempt on non-webhook view |
| DJ019 | .count() > 0, use .exists() |
| DJ020 | select_related() without arguments follows ALL FKs |
| DJ022 | Mutable default on JSONField |
| DJ026 | .save()/.create() in loop, use bulk_create() |
| DJ027 | Celery .delay() inside transaction.atomic() |
| DJ030 | DRF AllowAny or empty permission_classes |
| DJ032 | Django ValidationError in DRF code causes 500s |
| Code | Issue |
|---|---|
| DJ101 | Model missing __str__ |
| DJ102 | Duplicate related_name |
| DJ103 | null=True on string field (graph-validated) |
| Code | Issue |
|---|---|
| DJ201 | Invalid field in .filter()/.exclude()/.create() |
| DJ202 | Invalid field in .values()/.order_by() |
| DJ205 | Serializer Meta.fields references nonexistent model field |
| DJ207 | self.fk.id triggers DB query, use self.fk_id |
| Code | Issue |
|---|---|
| DJ012 | DEBUG = True in production |
| DJ013 | Missing SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, etc. |
| DJ016 | Hardcoded SECRET_KEY |
| Code | Issue |
|---|---|
| DV001 | Missing __str__ (MRO walk) |
| DV202 | Missing migrations |
| DV401 | Template syntax errors |
| DV501 | ModelForm field mismatches |
| DV601 | Unimportable dotted-path settings |
thorn-django . --check=fix # Bugs + security only
thorn-django . --check=improve # + Performance + deprecations (default)
thorn-django . --check=all # + Style + complexity# pyproject.toml
[tool.thorn]
exclude = ["*/migrations/*"]
ignore = ["DJ015"]
[tool.thorn-django]
settings = "myproject.settings.production"┌─────────────────────────────────────────────────────┐
│ thorn-django │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ AST Checks │ │ Graph Checks │ │ Cross │ │
│ │ (per-file) │ │ (per-model) │ │ Checks │ │
│ │ DJ001-041 │ │ DJ101-104 │ │ DJ201-207 │ │
│ └──────┬──────┘ └──────┬───────┘ └─────┬──────┘ │
│ │ │ │ │
│ │ ┌──────▼───────┐ │ │
│ │ │ Model Graph │◀────────┘ │
│ │ │ (AppGraph) │ │
│ │ └──────┬───────┘ │
│ │ │ │
│ │ ┌───────────┴──────────┐ │
│ │ │ │ │
│ │ ┌─▼──────────┐ ┌───────▼──────┐ │
│ │ │ PyO3 Bridge│ │ JSON file │ │
│ │ │ (in-proc) │ │(.thorn/*.json)│ │
│ │ └────────────┘ └──────────────┘ │
│ │ │
└─────────┼────────────────────────────────────────────┘
│
▼
┌──────────────┐
│ thorn engine │ (thorn-api, thorn-core, thorn-cli)
└──────────────┘
# Static checks only
docker run --rm -v "$PWD:/src" ghcr.io/pescheckit/thorn-django:latest .
# With Django model introspection
docker run --rm -v "$PWD:/src" ghcr.io/pescheckit/thorn-django:latest . --django-settings=myproject.settings
# Pin to a specific version
docker run --rm -v "$PWD:/src" ghcr.io/pescheckit/thorn-django:0.1.9-alpha .COPY --from=ghcr.io/pescheckit/thorn-django:latest /usr/local/bin/thorn-django /usr/local/bin/thorn-django# Auto-detect via PyO3 (Django booted in-process)
thorn-django . --django-settings=myproject.settings
# Or pre-generate and cache (useful for CI)
python -m thorn_django --settings myproject.settings
thorn-django .# Requires Python 3.11+ dev headers
cargo build --release
# Run tests via Docker
docker compose run --rm devMIT