Django — Claude Code rules
Follow Django's official coding style, ORM optimization, and security guidance. Rules below are drawn from the Django docs; don't invent conventions.
Style
- Follow PEP 8. Format all code with black (88-char line limit in code; wrap docs/comments/docstrings at 79).
- 4 spaces for indentation in Python; 2 spaces in HTML/templates.
underscore_casefor variables, functions, methods (poll.get_unique_voters(), notgetUniqueVoters()).InitialCapsfor class names.- String interpolation: %-formatting, f-strings, or
str.format()— whichever is most readable. Don't put calls or arithmetic inside an f-string (f"hello {get_user()}"is out); assign to a local first. - Never use f-strings for anything translatable (error/log messages). Use
format()and mark strings for i18n. - In views, the first parameter is always
request, neverreq. - Remove unused imports and trailing whitespace. Don't put your name in code.
Imports
- Sort imports with isort. Group order: future, standard library, third-party, other Django components, local component, try/except. Alphabetize each group by full module name; put
import modulebeforefrom module import …. - Absolute imports for other Django components; one-dot relative (
from .models import …) for local. Avoid multi-dot relative imports. - Use convenience imports when available:
from django.views import View, not the deep path.
Models & ORM
- Field names are all lowercase with underscores. Put
class Metaafter the fields, separated by one blank line. - Member ordering: fields → custom manager attrs →
Meta→__str__/magic methods →save()→get_absolute_url()→ custom methods. - Define
choicesas a mapping with all-uppercase class-attribute names, or usemodels.TextChoices/IntegerChoices. - Avoid N+1. Use
select_related()for ForeignKey / OneToOne (SQL JOIN); useprefetch_related()for ManyToMany and reverse FK/O2O (separate queries joined in Python). - QuerySets are lazy and cache results once evaluated. Store
group.members.all()in a variable and reuse it rather than re-querying. - Use
count()/exists()/len()for their specific intent — but if you need the rows too, evaluate the QuerySet once instead of callingexists()thenall()thencount(). - Use
values()/values_list()when you only need dicts/lists, not model objects. Useonly()/defer()to skip heavy columns (profile first). - Do work in the DB:
filter()/exclude()for filtering,F()expressions for same-row fields,annotate()for aggregation. PreferQuerySet.update()/bulk_create()/bulk_update()and bulk M2Madd()/remove()over per-objectsave()loops. - Access the FK id directly (
entry.blog_id) instead ofentry.blog.idto avoid a fetch. AddMeta.indexes/db_indexfor frequently filtered fields; profile withQuerySet.explain().
Security
- Keep
{% csrf_token %}in every POST form and keepCsrfViewMiddlewareenabled. Use@csrf_exemptonly when absolutely necessary. - Rely on the ORM's query parameterization for SQL safety. Use
raw()/extra()/RawSQL()sparingly and always escape user-controlled parameters. - Let template auto-escaping protect against XSS. Be very careful with
mark_safe, thesafetag,is_safe, or autoescape-off, and with storing/rendering user HTML. - Set
ALLOWED_HOSTSexplicitly. Read the host viarequest.get_host(), notrequest.METAdirectly (which bypasses validation). - In production:
DEBUG = False, keepSECRET_KEYsecret, deploy behind HTTPS withSECURE_SSL_REDIRECT,SESSION_COOKIE_SECURE,CSRF_COOKIE_SECURE, and HSTS (SECURE_HSTS_SECONDSetc.). - Keep clickjacking protection (
X-Frame-Optionsmiddleware) on. Validate all user input via forms. Limit upload/request body size at the web server; serve user-uploaded content from a separate domain.
Don't
- Don't access
django.conf.settingsat module top level (import time) — it breakssettings.configure(). Use lazy indirection (LazyObject,lazy(), orlambda). - Don't disable CSRF, escaping, or
ALLOWED_HOSTSvalidation for convenience. - Don't loop and
.save()when a singleupdate()/bulk_*call works. - Don't query inside a loop when
select_related/prefetch_relatedwould fetch it all at once. - Don't order results (
Meta.ordering) when you don't need them — callorder_by()with no args to drop it.