49 lines | 4.4 KB

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_case for variables, functions, methods (poll.get_unique_voters(), not getUniqueVoters()). InitialCaps for 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, never req.
  • 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 module before from 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 Meta after the fields, separated by one blank line.
  • Member ordering: fields → custom manager attrs → Meta__str__/magic methods → save()get_absolute_url() → custom methods.
  • Define choices as a mapping with all-uppercase class-attribute names, or use models.TextChoices/IntegerChoices.
  • Avoid N+1. Use select_related() for ForeignKey / OneToOne (SQL JOIN); use prefetch_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 calling exists() then all() then count().
  • Use values() / values_list() when you only need dicts/lists, not model objects. Use only() / 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. Prefer QuerySet.update() / bulk_create() / bulk_update() and bulk M2M add()/remove() over per-object save() loops.
  • Access the FK id directly (entry.blog_id) instead of entry.blog.id to avoid a fetch. Add Meta.indexes / db_index for frequently filtered fields; profile with QuerySet.explain().

Security

  • Keep {% csrf_token %} in every POST form and keep CsrfViewMiddleware enabled. Use @csrf_exempt only 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, the safe tag, is_safe, or autoescape-off, and with storing/rendering user HTML.
  • Set ALLOWED_HOSTS explicitly. Read the host via request.get_host(), not request.META directly (which bypasses validation).
  • In production: DEBUG = False, keep SECRET_KEY secret, deploy behind HTTPS with SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, and HSTS (SECURE_HSTS_SECONDS etc.).
  • Keep clickjacking protection (X-Frame-Options middleware) 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.settings at module top level (import time) — it breaks settings.configure(). Use lazy indirection (LazyObject, lazy(), or lambda).
  • Don't disable CSRF, escaping, or ALLOWED_HOSTS validation for convenience.
  • Don't loop and .save() when a single update() / bulk_* call works.
  • Don't query inside a loop when select_related / prefetch_related would fetch it all at once.
  • Don't order results (Meta.ordering) when you don't need them — call order_by() with no args to drop it.