LearnThatStack Ace your next interview

Django

Django 6.x. Newer features name their version. Checked Nov 2026 145 code snippets

The model

  • MVT splits an app into a model for the data, a view for one request's logic and a template for the page. The framework itself plays MVC's controller.
  • A project holds the settings and root URLs. An app is one feature inside it, switched on in INSTALLED_APPS.
  • Middleware wraps the view in layers. A request walks down the MIDDLEWARE list, and the response walks back up.
  • A QuerySet is lazy. It runs no SQL until you use its rows, and then it caches them.
  • Django blocks the common attacks by default. Most holes appear where code turns a default off, as with |safe, @csrf_exempt or SQL built by string formatting.
App serverGunicorn, UvicornMiddlewareMIDDLEWARE listURL resolverurlpatternsViewORM, templateINHttpRequesttop-downfirst matchOUTHttpResponsereverse orderEARLYearly response
A request goes IN through middleware to one view, and the response comes OUT in reverse. Any middleware can answer early, so the view never runs.

Projects, apps & requests

how the pieces fit and what runs when

URLs & views

route a request and answer it

Route URLs to views

Capture a value with a converter or a regexpath('a/<int:id>/', view)re_path(r'^(?P<y>[0-9]{4})/$', view)
List the path convertersstr, int, slug, uuid and path. Only path matches slashes.
Send the rest of the URL to an appinclude('blog.urls')
Link to a named pattern{% url 'list' %}reverse('list')

Tipurls.py picks the view, and views.py holds the code.

TrapDjango uses the first match, so order matters. Matching ignores the domain and the query string.

Function views and class views

Branch on the verb in a functionif request.method == 'POST':
Give each verb a method in a classdef post(self, request):ArticleView.as_view()
Stop a refresh from posting twicereturn redirect('done')

TipUse a function for one-off logic and a class for a common pattern or shared code.

TrapA function that branches only on GET and POST returns nothing for other verbs. Add a branch or @require_http_methods.

Generic views and decorators

List objects in pages, or show oneListViewpaginate_by = 10DetailView
Create, edit or delete with a formCreateViewUpdateViewDeleteView
Require a login, on a function or a class@login_requiredLoginRequiredMixin

TipUse generic views for standard CRUD, not unusual logic.

TrapThe top decorator runs first, so put @login_required above the method check.

Models & migrations

define the data and change the schema

Link models and choose on_delete

Link many to onemodels.ForeignKey
Link one to one, to extend the usermodels.OneToOneField
Link many to many with a join tablemodels.ManyToManyField
Delete the dependents toomodels.CASCADE
Block the delete while dependents existmodels.PROTECTmodels.RESTRICT
Reset the link, which for SET_NULL needs null=Truemodels.SET_NULLmodels.SET_DEFAULT
Let the database apply the rule (6.1+)models.DB_CASCADE

TrapDB_CASCADE is faster because Django fetches no related rows, but it sends no delete signals.

Model inheritance

abstract = TrueShare fields such as timestamps
class Restaurant(Place):Query every child through its parent
proxy = TrueChange only ordering, manager or methods
has its own tableno table

TrapMulti-table inheritance links two tables with a OneToOneField, so loading a child reads both.

Migrations

Write migration files from model changespython manage.py makemigrations
Apply thempython manage.py migrate
Roll back to an earlier onepython manage.py migrate blog 0006
Print a migration's SQL firstpython manage.py sqlmigrate blog 0007

TrapA rollback works only if the migrations you undo are reversible.

Migrate safely in production

Old codeignores itNew codewrites columnBackfillbatchesColumnnullablerequiredtime

Add a required column in steps. Old and new code overlap during a deploy, so it stays nullable until the backfill ends.

Drop a columnMake it nullable, stop using it, then drop it in a later release.
Backfill in batches, each a short transactionatomic = False
Build an index and keep writes working (PostgreSQL)AddIndexConcurrently

TrapMySQL can't roll back a failed schema change, so you fix it by hand.

ORM queries & transactions

read and write data without extra queries

Query with the ORM

Explain what the ORM doesIt maps classes to tables and objects to rows.
Get all rows, or the rows that matchArticle.objects.all().filter(live=True)
Get exactly one object, at once.get(pk=1)
Get one object, or else None or a 404.filter(pk=1).first()get_object_or_404(Article, pk=1)

TipThe ORM gives one API across databases, parameters that block SQL injection, and migrations.

Trapget() raises DoesNotExist or MultipleObjectsReturned, so use it on unique fields.

Lazy QuerySets

Make a QuerySet run its SQLfor a in qs:list(qs)bool(qs)
Count rows, or test for one, without loading themqs.count()qs.exists()

TrapChaining another .filter() builds a new QuerySet, and that one queries again.

Complex filters and summaries

Filter with OR or NOTQ(a=1) | Q(b=2)~Q(title='')
Compare two fields, or decrement with no race conditionfilter(stock__lt=F('min'))update(stock=F('stock') - 1)
Get one summary as a dictionary, which ends the chainaggregate(Avg('price'))
Add a value to each object, then filter on itannotate(n=Count('book'))filter(n__gt=5)

TipIn one filter() call, Q arguments must come before keyword arguments.

TrapSeveral Count() calls across relations multiply rows and give wrong totals. Pass distinct=True.

Fix N+1 queries

TipN+1 means one query for the list, then one more per item that touches a related object.

Trapauthor.books.filter() in the loop ignores the prefetch and queries again.

Transactions

Make queries succeed or fail togetherwith transaction.atomic():
Lock rows inside a transactionselect_for_update()
Send email only after a committransaction.on_commit(fn)

TipATOMIC_REQUESTS puts every view in a transaction, but it adds overhead.

TrapCatch database errors around an atomic block, not inside it, or Django may miss the broken transaction.

Templates & forms

draw pages and check what users send

Templates and inheritance

Print a value, change it, or loop{{ article.title }}{{ text|lower }}{% for a in qs %}
Explain auto-escapingIt turns < into &lt;, which blocks most cross-site scripting.
Mark a part in the base, then extend it{% block content %}{% extends 'base.html' %}
Add to the parent's block instead of replacing it{{ block.super }}

Trap{% extends %} must be the child's first template tag.

Custom tags and filters

Register a filter, a simple tag or an inclusion tag@register.filter@register.simple_tag@register.inclusion_tag

TipLoad the module in a template with {% load custom_tags %}.

Traptemplatetags needs an __init__.py, and the dev server needs a restart.

Forms and validation

Define fields by hand, or generate them from a modelforms.Formforms.ModelForm
Check the POST data and read clean valuesform.is_valid()form.cleaned_data
Check one field, then rules across fieldsdef clean_title(self):def clean(self):

Tipis_valid() runs field checks, then clean_<field>(), then clean().

TrapList fields explicitly, or users can set fields they shouldn't.

Login, roles & sessions

who the user is and what they may do

Log in and custom users

Check credentials, which gives None if they failauthenticate(request, **creds)
Save the user in the session, or log outlogin(request, user)logout(request)
Keep the built-in user fields and add yoursAbstractUser
Define every field yourself, plus a managerAbstractBaseUser
Log in with a unique emailUSERNAME_FIELD = 'email'

TipIn other code, use get_user_model(), never User.

TrapSet AUTH_USER_MODEL before the first migration. Changing it later is complex.

Permissions and roles

Add your own beside add, change, delete and view('publish', 'Can publish')Put it in Meta.permissions.
Check one in code, a function view or a class viewuser.has_perm('blog.publish')@permission_requiredPermissionRequiredMixin
Use a group as a rolegroup.permissions.add(perm)user.groups.add(group)

TipPermissions are model-level. Per-object rules need a custom backend or a package.

TrapCheck permissions, not group names. has_perm() keeps working when you rename a role.

Sessions

Write session datarequest.session['cart'] = cart
Block session fixationDjango makes a new session key on login.
Purge expired sessions regularlypython manage.py clearsessions

TipBy default the database keeps the data, and the cookie holds only a session ID. Cookie-based sessions put the data in the cookie, where the client can read it.

TrapDjango saves only when you assign or delete a key. Assign a changed list back.

Middleware, signals & admin

run code around requests and saves

Write middleware

class Timer: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # before the view res = self.get_response(request) # after the view return res
Stop before the viewReturn a response and skip get_response.
Use the older hooks, like process_response()MiddlewareMixin

TipAdd the class's dotted path to MIDDLEWARE.

TrapOnly a middleware listed below AuthenticationMiddleware can read request.user.

Signals

Run code after a save@receiver(post_save, sender=User)
Decide when to use oneFor side effects in another app, not core logic.

TipConnect receivers in AppConfig.ready(). They run one at a time before send() returns.

TrapQueryset update() and bulk_create() send no save signals.

REST APIs & tests

serve JSON, limit it and test it

REST APIs and rate limits

Return a list as JSON in plain DjangoJsonResponse(items, safe=False)
Get full CRUD for a model in DRFviewsets.ModelViewSet
Generate serializer fields from a model, or declare themModelSerializerSerializer
Limit a view per IP with django-ratelimit, which gives 403@ratelimit(key='ip', rate='5/m')
Limit a DRF APIAnonRateThrottleUserRateThrottle

TipPlain Django suits a few simple endpoints. Django REST Framework (DRF) suits many, or outside clients.

TrapThe local-memory cache keeps one counter per process. Use a shared cache such as Redis.

Tests, fixtures and factories

Run the testspython manage.py test
Test with no database, rollback per test, or real transactionsSimpleTestCaseTestCaseTransactionTestCase
Load static data files in a testfixtures = ['blog.json']
Build data in code with factory_boyfactory.django.DjangoModelFactory

TrapFixtures go stale when models change, and a test can depend on a pk=1 hidden in a file.

Caching, pools & tasks

make pages fast and move slow work out

Speed up pages and connections

Check a query's plan, then index the fieldqs.explain()db_index=True
Load only the columns you needonly('title')defer('body')
Keep each thread's connection open'CONN_MAX_AGE': 600
Pool per process on PostgreSQL (5.1+)'OPTIONS': {'pool': True}
Pool across all workersPut PgBouncer in front of the database.

TipMeasure first. Django Debug Toolbar shows each page's queries and their time.

TrapCONN_MAX_AGE must stay 0 when the built-in pool is on.

Cache at each level

Cache every pageUpdateCacheMiddlewareFetchFromCacheMiddleware
Cache one view, or part of a template@cache_page(60 * 15){% cache 500 sidebar %}
Cache any value for 30 secondscache.set('k', v, 30)cache.get('k')

TrapPages that differ per user need the right Vary headers, or users may get each other's pages.

Background tasks

Move slow work out of the requestThe view enqueues a job, and a worker runs it, usually Celery.
Use Django's Tasks framework (6.0+)from django.tasks import taskIt only enqueues. Production needs a real worker.

TipPass IDs, not model objects, and enqueue from on_commit().

TrapA retry can run a task again, so make tasks idempotent, meaning safe to run twice.

Security & deployment

what to set and check before release

CSRF and common holes

Send the token from a form, or with AJAX{% csrf_token %}X-CSRFToken
Explain the checkThe form's secret must match the cookie's, or Django returns 403.
Pass values safely in raw SQLArticle.objects.raw(sql, [value])
Add a second XSS layer (6.0+)Turn on the built-in Content Security Policy.

TrapDjango skips the CSRF check on safe methods such as GET, so a GET view must never change data.

Production settings

Turn off debug, list your domains, and hide the secret keyDEBUG = FalseALLOWED_HOSTS = ['example.com']os.environ['SECRET_KEY']
Force HTTPS and secure cookiesSECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = True
Report missing settings before a releasepython manage.py check --deploy

TrapStart SECURE_HSTS_SECONDS small. A wrong setting can break the site for as long as that value lasts.

Static and media files

Copy static files into STATIC_ROOTpython manage.py collectstatic
Set the storage backends (4.2+)STORAGESIt replaced DEFAULT_FILE_STORAGE, removed in 5.1.

TipThe web server or a CDN serves these files, never Django.

TrapUploads are untrusted. Limit their size, never execute them and serve them from a separate domain.

WSGI and ASGI

Serve with sync worker processes (WSGI)gunicorn mysite.wsgi:application
Serve WebSockets and long polling (ASGI)uvicorn mysite.asgi:application

TrapAsync views under WSGI each get a one-off event loop and lose the benefit for long requests.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 56 questions it links, not just the 7 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.