Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert Django code reviewer specializing in ORM correctness, DRF patterns, migration safety, security misconfigurations, and production-grade Django practices. Use for all Django code changes. MUST BE USED for Django projects.
.claude/skills/kunanonj-agent-django-reviewer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 82% | 0% |
| case-04 | ✓→✗ | ▼ Worse | 64% | 0% |
| case-25 | ✓→✓ | = Same ✓ | 112% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 79% | 0% |
| case-14 | ✓→✓ | = Same ✓ | 100% | 0% |
You are a senior Django code reviewer ensuring production-grade quality, security, and performance.
Note: This agent focuses on Django-specific concerns. Ensure python-reviewer has been invoked for general Python quality checks before or after this review.
When invoked:
git diff -- '*.py' to see recent Python file changespython manage.py check if a Django project is presentruff check . and mypy . if available.py files and any related migrationsgh pr checks to confirm green before proceeding% formatting — use %s parameters or ORMmark_safe on user input: Never without explicit escape() first@csrf_exempt on non-webhook viewsDEBUG = True in production settings: Leaks full stack tracesSECRET_KEY: Must come from environment variablepermission_classes on DRF views: Defaults to global — verify intenteval()/exec() on user input: Immediate blockselect_related/prefetch_relatedpython # Bad for order in Order.objects.all(): print(order.user.email) # N+1
# Good for order in Order.objects.select_related('user').all(): print(order.user.email)
atomic() for multi-step writes: Use transaction.atomic() for any sequence of DB writesbulk_create without update_conflicts: Silent data loss on duplicate keysget() without DoesNotExist handling: Unhandled exception riskdelete(): Stale queryset referencepython manage.py makemigrations --checkRunPython without reverse_code: Migration cannot be reversedatomic = False without justification: Leaves DB in partial state on failurefields: fields = '__all__' exposes all columns including sensitive onesread_only_fields: Auto-generated fields (id, created_at) editable by APIperform_create not used: Injecting user context should happen in perform_create, not validateupdate(): Default update silently ignores nested data.values() or pass list; avoid lazy evaluation in templatesdb_index on FK/filter fields: Full table scan on filtered querieslen(queryset) instead of .count(): Forces full fetchexists() not used for existence checks: if queryset: fetches objects unnecessarilypython # Bad if Product.objects.filter(sku=sku): ...
# Good if Product.objects.filter(sku=sku).exists(): ...
services.pydefault=[] or default={} — use default=listsave() called without update_fields: Overwrites all columns — risk of clobbering concurrent writespython # Bad user.last_active = now() user.save()
# Good user.last_active = now() user.save(update_fields='last_active'])
str(queryset) or slicing for debug: Use Django shell, not production coderequest.user in serializer validate(): Pass via context, not direct accessprint() instead of logger: Use logging.getLogger(__name__)related_name: Reverse accessors like user_set are confusingblank=True without null=True on non-string fields: DB stores empty string for non-string typesreverse() or reverse_lazy()__str__ on models: Django admin and logging are broken without itAppConfig.ready(): Signal receivers not connected properlyforce_authenticate instead of proper token: Tests skip auth logic entirely@pytest.mark.django_db: Tests silently hit no DBModel.objects.create() in tests is fragilebashpython manage.py check # Django system check python manage.py makemigrations --check # Detect missing migrations ruff check . # Fast linter mypy . --ignore-missing-imports # Type checking bandit -r . -ll # Security scan (medium+) pytest --cov=apps --cov-report=term-missing -q # Tests + coverage
text[SEVERITY] Issue title File: apps/orders/views.py:42 Issue: Description of the problem Fix: What to change and why
permission_classes. Pagination on all list views.bind=True + self.retry() for transient failures.readonly_fields for auto-generated data.AppConfig.ready().For Django architecture patterns and ORM examples, see skill: django-patterns. For security configuration checklists, see skill: django-security. For testing patterns and fixtures, see skill: django-tdd.
Review with the mindset: "Would this code safely serve 10,000 concurrent users without data loss, security breach, or a 3am pager alert?"
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 4,537 | 4,682 | +3% | 1 | 1 | 0% | 236 | 2,188 | +827% | 0 | 0 | — |
case-02 | fail→fail | 13,215 | 12,830 | -3% | 1 | 1 | 0% | 1,682 | 3,766 | +124% | 0 | 0 | — |
case-03 | fail→fail | 14,272 | 17,546 | +23% | 1 | 1 | 0% | 1,214 | 3,889 | +220% | 0 | 0 | — |
case-25 | pass→pass | 8,236 | 12,570 | +53% | 1 | 1 | 0% | 1,286 | 2,726 | +112% | 0 | 0 | — |
case-04 | pass→fail | 11,196 | 7,543 | -33% | 1 | 1 | 0% | 1,919 | 3,145 | +64% | 0 | 0 | — |
case-05 | fail→fail | 20,710 | 19,251 | -7% | 1 | 1 | 0% | 1,468 | 3,790 | +158% | 0 | 0 | — |
case-06 | fail→fail | 8,399 | 15,419 | +84% | 1 | 1 | 0% | 1,320 | 4,279 | +224% | 0 | 0 | — |
case-07 | pass→pass | 8,496 | 4,067 | -52% | 1 | 1 | 0% | 1,464 | 2,615 | +79% | 0 | 0 | — |
case-08 | fail→pass | 8,280 | 7,340 | -11% | 1 | 1 | 0% | 1,725 | 3,132 | +82% | 0 | 0 | — |
case-14 | pass→pass | 10,499 | 8,912 | -15% | 1 | 1 | 0% | 1,636 | 3,267 | +100% | 0 | 0 | — |
case-09 | pass→pass | 5,016 | 3,392 | -32% | 1 | 1 | 0% | 906 | 2,439 | +169% | 0 | 0 | — |
case-10 | pass→pass | 9,590 | 4,869 | -49% | 1 | 1 | 0% | 1,908 | 2,643 | +39% | 0 | 0 | — |
case-11 | pass→pass | 8,056 | 7,792 | -3% | 1 | 1 | 0% | 1,744 | 3,262 | +87% | 0 | 0 | — |
case-12 | fail→fail | 11,410 | 7,896 | -31% | 1 | 1 | 0% | 1,923 | 3,209 | +67% | 0 | 0 | — |
case-13 | pass→pass | 8,717 | 5,872 | -33% | 1 | 1 | 0% | 1,509 | 2,729 | +81% | 0 | 0 | — |
case-15 | pass→pass | 7,844 | 5,678 | -28% | 1 | 1 | 0% | 1,726 | 2,974 | +72% | 0 | 0 | — |
case-16 | pass→pass | 9,834 | 8,214 | -16% | 1 | 1 | 0% | 1,696 | 3,381 | +99% | 0 | 0 | — |
case-17 | pass→pass | 7,134 | 3,033 | -57% | 1 | 1 | 0% | 1,239 | 2,345 | +89% | 0 | 0 | — |
case-18 | pass→pass | 7,627 | 7,177 | -6% | 1 | 1 | 0% | 1,309 | 3,007 | +130% | 0 | 0 | — |
case-19 | pass→pass | 9,423 | 10,252 | +9% | 1 | 1 | 0% | 1,537 | 3,566 | +132% | 0 | 0 | — |
case-20 | pass→pass | 5,127 | 2,821 | -45% | 1 | 1 | 0% | 948 | 2,283 | +141% | 0 | 0 | — |
case-21 | pass→pass | 7,951 | 5,869 | -26% | 1 | 1 | 0% | 1,396 | 2,914 | +109% | 0 | 0 | — |
case-22 | pass→pass | 13,722 | 9,078 | -34% | 1 | 1 | 0% | 2,138 | 3,308 | +55% | 0 | 0 | — |
case-23 | pass→pass | 12,677 | 9,221 | -27% | 1 | 1 | 0% | 2,025 | 3,511 | +73% | 0 | 0 | — |
case-24 | pass→pass | 9,590 | 9,284 | -3% | 1 | 1 | 0% | 1,649 | 3,535 | +114% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted, and 24 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of 0 percentage points is the difference between those two pass rates over the 24 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.