Code Reviewer — Django Overlay
This skill extends code-reviewer (the universal skill). Always apply the universal
skill's full checklist first, then apply the Django-specific rules in this file on top.
Composition order:
- Run
code-reviewer(universal pillars: correctness, security, performance, DRY, tests, docs) - Run this overlay (Django/Python-specific rules below)
- Report findings from both in a single unified output
Step 0 — Detect Versions First (always, before reviewing anything)
Run these commands before touching any code. Version determines which rules apply.
# Python version
python --version
cat .python-version 2>/dev/null
# Django version
python -c "import django; print(django.get_version())"
# Key package versions
pip show djangorestframework celery | grep -E "^(Name|Version)"
# Check if project uses ASGI or WSGI
grep -r "application = " */asgi.py */wsgi.py 2>/dev/null | head -5
Report at the top of your review:
🔍 Environment: Python X.Y | Django X.Y | DRF X.Y (if present) | WSGI/ASGI
Then apply the version-specific rules below that match.
Django Version Rules
Django 3.x
- ORM is fully synchronous — never use
async defviews with ORM calls withoutsync_to_async(). FlagSynchronousOnlyOperationrisk. - Async views available from 3.1 but ORM is not async-safe — flag any async view that calls the ORM directly without wrapping.
- No
acreate(),aget(),afilter()— these don't exist yet; flag if used. X_FRAME_OPTIONSdefaults toDENYsince 3.0 — flag any override that weakens it.
Django 4.x
- 4.1+: Async ORM methods available (
acreate,aget,asave,adelete,aiterator) — prefer these in async views oversync_to_async()wrapping. - 4.1+: Async class-based views supported — verify
async def get()/async def post()handlers are used correctly. - 4.2 LTS: Last version to support Python 3.8/3.9 — flag if project targets 4.2 but uses Python 3.10+ features.
- Transactions still do not work in async mode in 4.x — flag
asyncviews that usetransaction.atomic()without sync wrapping. Signal.asend()/Signal.asend_robust()not yet available — flag async signal dispatch attempts.
Django 5.x
- 5.0+:
GeneratedFieldanddb_defaultare available — flag Python-side computed defaults that could bedb_default=Now()or aGeneratedField. - 5.0+: Most decorators now wrap async views — flag any manual
sync_to_async(decorator)workarounds that are now unnecessary. - 5.0+:
asyncio.CancelledErroravailable for async disconnect cleanup — flag missing cleanup in async views with long-held connections. - 5.2 LTS: MySQL 5.7 is no longer supported — flag
mysql-connectoror settings targeting MySQL < 8.0. - 5.2+: Async auth methods available (
acheck_password, async permissions) — flag sync auth calls in async views.
Python Version Rules
- Python 3.8/3.9: No
match/case, noX | Yunion types, nodict | dictmerge operator. Flag if used on these versions. - Python 3.10+:
match/caseavailable — flag verboseif/elifchains on Django model state that could be amatch. - Python 3.12+/3.13+: Preferred for new projects. Use
target-version = "py312"or"py313"inruff.toml. - Always flag: missing type hints on function signatures in a project that has adopted typing.
Django-Specific Review Checklist
Work through each section. Skip if not relevant to the files being reviewed.
🗄️ ORM & QuerySet
- N+1 queries — flag any loop that accesses a related object without
select_related()orprefetch_related().# ❌ N+1 for order in Order.objects.all(): print(order.user.email) # query per order # ✅ Fixed for order in Order.objects.select_related('user'): print(order.user.email) - Raw SQL — flag
cursor.execute()orModel.objects.raw()with f-strings or string concatenation. Must use parameterized queries:cursor.execute("... WHERE id = %s", [user_input]). - Bulk operations — flag loops with
.save()inside that could usebulk_create()orbulk_update(). - QuerySet evaluation — flag accidental early evaluation (e.g.
list()on a queryset before filtering is complete). only()/defer()— flag queries that load all fields when only a subset is needed.values()/values_list()— suggest for read-only, non-model use cases to reduce memory overhead.exists()vscount()— flagcount() > 0checks; useexists()instead.get()without try/except — flag bareModel.objects.get()without catchingDoesNotExist.- Async ORM (4.1+) — in async views, flag sync ORM calls not wrapped in
sync_to_async()or not usinga-prefixed methods.
🏗️ Models
CharFieldandTextField— flagnull=Trueon string fields; Django convention isblank=Trueonly (empty string, not NULL).ForeignKey— flag missingon_deleteargument (required since Django 2.0).ForeignKey— flag missingrelated_nameon fields that will be reverse-accessed.- Custom managers — flag overriding
get_queryset()without callingsuper(). Meta.ordering— flag models withorderingset that also use.order_by()everywhere (redundant).__str__— flag models missing a__str__method.- Migrations — flag
makemigrationsskipped after model changes (check if migration files match model state). GeneratedField(5.0+) — suggest replacing@propertyfields that only read other model fields.
👁️ Views & URLs
- Fat views — flag views doing business logic directly; suggest moving to service layer or model methods.
get_object_or_404— flag bareModel.objects.get()in views whereget_object_or_404is more appropriate.- Permission checks — flag views missing
@login_required,permission_required, orIsAuthenticated(DRF). - CSRF — flag views that disable CSRF (
@csrf_exempt) without documented justification. - Class-based views — flag overriding
dispatch()for logic that belongs inget()/post(). - URL naming — flag hardcoded URL strings in views; use
reverse()or{% url %}.
🔌 Django REST Framework (if present)
- Serializer validation — flag missing
validate_<field>()orvalidate()methods for business rules. SerializerMethodField— flag methods that hit the database (N+1 risk in list views).ViewSetvsAPIView— flagAPIViewused where aViewSet+ router would reduce boilerplate.- Throttling — flag APIs missing
DEFAULT_THROTTLE_CLASSESfor anonymous endpoints. depthon serializers — flagdepth > 1; prefer explicit nested serializers for control.- Pagination — flag list endpoints missing pagination class.
permission_classes = []— flag explicitly empty permissions (open endpoint) without comment.
🔐 Django Security (extends universal security pillar)
DEBUG = Truein any non-development settings file — critical, must flag.SECRET_KEYhardcoded in settings — must come from environment variable.ALLOWED_HOSTS = ['*']in production settings — flag.- Missing security headers in production settings:
SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 X_FRAME_OPTIONS = 'DENY' SECURE_CONTENT_TYPE_NOSNIFF = True |safein templates — flag any use; verify it's intentional and the value is truly safe.- User enumeration — flag login views that reveal whether an email exists via different error messages.
- Raw SQL injection — see ORM section above. Specifically flag PostGIS backend usage with user input and unsanitized geographic query parameters.
⚙️ Settings & Configuration
- Flag a single
settings.pythat mixes dev and production config — suggestsettings/base.py,settings/dev.py,settings/production.py. - Flag
INSTALLED_APPScontaining debug tools (debug_toolbar,django_extensions) in production settings. - Flag missing
DEFAULT_AUTO_FIELD(causes warnings in Django 3.2+). - Flag
DATABASESwith hardcoded credentials — must useos.environ.get()ordjango-environ.
🧪 Testing
- Flag test files not using
pytest-djangoorTestCasefromdjango.test. - Flag tests using the production database instead of
@pytest.mark.django_dborTransactionTestCase. - Flag missing
setUpTestData()for expensive setup shared across test methods. - Flag tests that test Django internals (ORM, forms) rather than app behavior.
- Flag missing
assertRaisesMessage/assertFormErrorwhere appropriate.
⚡ Async (version-gated)
- Flag async views calling sync ORM without
sync_to_async()(all versions). - Flag
transaction.atomic()inside async views without sync wrapping (all versions — transactions are not async-safe). - Flag missing
awaitona-prefixed ORM methods (await Model.objects.acreate(...)) — 4.1+. - Flag async views deployed under WSGI — they work but with performance penalty; suggest ASGI.
- Flag
sync_to_async(decorator)workarounds for decorators that natively support async (5.0+).
🌿 Celery (if present)
- Flag tasks not decorated with
@shared_taskor@app.task. - Flag tasks that are not idempotent (can fail silently if retried).
- Flag missing
bind=Trueon tasks that needself.retry(). - Flag database operations in tasks without
transaction.on_commit()wrapper (risk of operating on uncommitted data). - Flag hardcoded
CELERY_BROKER_URL— must come from environment.
🧹 Code Style & Tooling
- Flag missing
ruffconfiguration (pyproject.tomlorruff.toml) in new projects. - Flag
print()statements left in production code — uselogging. - Flag bare
except:clauses — must beexcept Exceptionat minimum, with logging. - Flag imports not organized (stdlib → third-party → local) —
isortorruffhandles this. - Flag f-strings used in logging calls:
logger.info(f"...")→ uselogger.info("...", extra={...}).
Unified Output Format
Use the same format as code-reviewer (universal). Add a Django context line:
🔍 Environment: Python 3.12 | Django 5.2 LTS | DRF 3.15 | ASGI
## Code Review Summary
[... standard universal format ...]
### 🐍 Django-Specific Issues
[Issues found by this overlay, using the same severity/format as universal]
Behavior Rules (Django-specific additions)
- Version first, always — never apply version-specific rules without confirming the version. Wrong rules on wrong versions produce wrong suggestions.
- ORM safety over cleverness — always prefer ORM over raw SQL. When raw SQL is necessary, parameterized queries are non-negotiable.
- Don't suggest async migrations unless version supports it — async ORM is only reliable from 4.1+.
- Settings files are high-risk — treat any change to
settings/production.pyas security-critical scope. - Delegate deep security audits →
security-auditorskill if available.