Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.
Python Django Conventions
Django project conventions: fat models thin views, queryset optimization with select_related and prefetch_related, migrations discipline, forms/serializers for validation, per-environment settings, and app boundaries.
Mby @markdownersPublished August 21, 2026 · ~4 min read
0 downloads · Used by 0 stacks
Push business logic into models and managers, keep views as thin coordinators that call into that logic and render a response — a view with more than a few lines of domain logic is a sign that logic belongs somewhere else.
Fat models, thin views
- Put domain logic (state transitions, computed properties, validation rules that always apply) on the model or a custom manager/queryset method, not in the view function or class — a
Order.mark_paid()method is reusable from a view, a management command, and a test; logic inlined in a view is not. - Keep views responsible for HTTP concerns only: parsing the request, calling the domain method, choosing the response — never let a view directly manipulate several related models' fields inline when a model method could encapsulate that transition.
- Extract logic shared by two or more views into a service function or model method before a third view needs it, not after.
Queryset optimization
- Use
select_relatedfor forward foreign-key/one-to-one relations andprefetch_relatedfor reverse foreign-key/many-to-many relations whenever a template or serializer will access them, to avoid the N+1 query pattern of one query per row. - Never access a related object in a loop over a queryset without having prefetched it first — profile with Django's query count (debug toolbar,
assertNumQueriesin tests) rather than assuming the ORM is doing the right thing. - Prefer
.values()/.values_list()or.only()/.defer()when a view genuinely needs a narrow slice of columns from a wide table, but don't reach for them by default — the readability cost of partial objects isn't worth it until a query is measurably expensive. - Use
.exists()to check presence and.count()to count — never fetch a full queryset just to checklen()or truthiness.
Migrations discipline
- Generate migrations with
makemigrationsimmediately after a model change and commit them in the same change as the model edit — a model and its migration must never drift apart in version control. - Never hand-edit a migration's schema operations after it has been applied anywhere outside your own unpushed branch; write a new migration instead.
- Keep data migrations (
RunPython) separate from schema migrations when both are needed for the same change, and always provide a reverse function (orRunPython.noopdeliberately) rather than leaving a migration irreversible by accident. - Run migrations against a staging/copy of production data before applying to production for any migration that touches a large table or changes a column's nullability/type.
Forms and serializers for validation
- Validate all external input (form POST, API payload) through a
Form/ModelFormor DRFSerializer, never by readingrequest.POST/request.datafields directly in a view and trusting their shape or type. - Put field-level validation (format, range) on the form/serializer field, and cross-field or business-rule validation in
clean()/validate()— don't scatter validation logic between the view and the model'ssave(). - Reuse the same serializer/form for both the API and any internal callers (admin actions, management commands) that create or update the same model, so validation rules only exist in one place.
Settings per environment
- Split settings into a shared base plus per-environment overrides (local, staging, production) — never branch on
if DEBUGscattered through arbitrary settings; keep environment-specific values (database, allowed hosts, email backend) in their own file or env-var-driven block. - Load secrets (
SECRET_KEY, database passwords, API keys) from environment variables, never hard-coded in a settings file that's committed to version control, including for "just local dev" — use a documented.env.exampleinstead. - Set
DEBUG = Falseand a realALLOWED_HOSTSin every environment that isn't a developer's own machine; aDEBUG = Truemisconfiguration in production leaks stack traces and settings to any visitor who hits an error.
App boundaries
- Design each Django app around one cohesive domain concept (e.g.,
billing,accounts), not around a technical layer (models,views) or a single feature ticket — an app should be something you could plausibly reuse or remove as a unit. - Avoid circular imports between apps by keeping cross-app references to IDs/foreign keys and importing the other app's public functions, not its internals; if two apps need to reach deep into each other constantly, they're probably one app.
- Keep an app's
models.pyas the single source of truth for that app's schema — don't define overlapping models for the same concept in two different apps.
Badge
Link back to this module from your own README.
[](https://markdowners.com/m/markdowners/python-django-conventions)Discussions about this module
No discussions about this module yet.
Start a discussion
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.