Checkpoint: Build a Django REST API
A checkpoint is a pause-and-consolidate milestone where you recall and practise everything just covered, and a REST API is a set of URL endpoints that exchange JSON so other programs can read and write your data.
Learn Checkpoint: Build a Django REST API in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this checkpoint you'll review the whole second half of the course — pagination, sessions, uploads, email, caching, management commands, DRF auth, and deployment — then prove it by designing a small JSON API with a paginated list, a token-protected create endpoint, and a cache layer, followed by a short quiz.
Paginator(queryset, per_page) splits a long list into pages. paginator.page(n) returns a Page with has_next , has_previous , and neighbour numbers for building links. You read the requested page from the ?page= query parameter and survive bad input with PageNotAnInteger and EmptyPage . A class-based ListView does it all with a single paginate_by attribute.
request.session behaves like a dictionary that survives across requests. You set , get with a default, and pop one-time values. SESSION_COOKIE_AGE controls the lifetime, the backend (db, cache, or signed cookie) controls where data lives, and flush() clears it on logout.
FileField and ImageField store a path under MEDIA_ROOT and serve it under MEDIA_URL . Uploads arrive in request.FILES , so you bind the form with both request.POST and request.FILES and add enctype="multipart/form-data" . upload_to routes files into subfolders.
send_mail fires a quick plain-text message; EmailMessage adds cc, bcc, and attachments; EmailMultiAlternatives sends HTML alongside text. EMAIL_BACKEND switches between the console backend in development and SMTP in production, and render_to_string builds bodies from templates.
The low-level API ( cache.set , get , add , delete ) stores values with a timeout. @cache_page caches a whole view and {' '} caches a template fragment. A timeout (TTL) makes entries expire, and unique cache keys keep one user's data from leaking to another.
A Command class subclassing BaseCommand in app/management/commands/ adds your own manage.py task. add_arguments declares the CLI, handle does the work, and self.stdout.write with self.style.SUCCESS produces styled output you can schedule with cron.
authentication_classes identify the caller (session, basic, or token), and TokenAuthentication reads an Authorization: Token … header. permission_classes like IsAuthenticated and IsAuthenticatedOrReadOnly decide access, a custom BasePermission enforces ownership, and throttling rate-limits clients with a 429.
Production means DEBUG=False , real domains in ALLOWED_HOSTS , and SECRET_KEY read from the environment. You collectstatic into STATIC_ROOT and serve it with WhiteNoise, run the app through gunicorn and WSGI, parse a DATABASE_URL , and audit with manage.py check --deploy .
A paginated API response carries the current page, the total number of pages, and the slice of results. The simulation below reproduces what Paginator plus a DRF serializer would return as JSON for a given page.
A create endpoint should reject anonymous callers. The example resolves the token to a user, returns 401 when it's missing or unknown, and otherwise creates the row — exactly the gate TokenAuthentication plus IsAuthenticated would enforce.
Time to put it together. Design a tiny Posts API that combines the three skills that pair up most often in real services:
The starter below has three ___ blanks — one per skill. Fill them in (per-page slice size, the "no user" value, and the cache key), then run to see the API respond. Try it yourself before opening the full solution.
A fuller version that wraps the API in a class, clamps out-of-range pages, supports two tokens, and invalidates the cached count on every write — the plain-Python core of a DRF view with pagination, TokenAuthentication , and the cache API:
⏱ Timed Quiz
Try to answer each in your head first, then expand to check.
A: Set the paginate_by attribute to the number of items per page, e.g. paginate_by = 10 . Django then builds the Paginator , reads ?page= from the URL, and adds page_obj , paginator , and is_paginated to the template context for you.
A: Read with a default — request.session.get("key", default) — so a missing key does not raise a KeyError . To clear everything on logout call request.session.flush() , which deletes the data and rotates the session key; Django's logout() view does this for you.
A: The HTML form needs enctype="multipart/form-data" , and the view must bind the form to both request.POST and request.FILES , e.g. MyForm(request.POST, request.FILES) . Miss either and the file silently never arrives.
A: timeout is the value's lifetime in seconds. Once it expires, a later cache.get returns None (a miss), so you recompute the value and store it again. It is the trade-off knob between fresher data (shorter) and faster pages (longer).
A: 401 Unauthorized means the request is not authenticated — log in or send a valid token. 403 Forbidden means the request is authenticated but the user is not allowed to perform that action. Authentication failures give 401; permission failures give 403.
A: Set DEBUG = False , list your real domains in ALLOWED_HOSTS , and read SECRET_KEY from the environment instead of hard-coding it. You also run collectstatic , serve the app via gunicorn/WSGI, and run manage.py check --deploy as a final audit.
Checkpoint complete — you can ship a real API!
You recapped pagination, sessions, uploads, email, caching, management commands, DRF authentication and permissions, and deployment — then designed a small JSON API with a paginated list, a token-protected create, and a cache layer, and worked the quiz.
🚀 Up next: Capstone — A Blog App — combine everything from the entire course into one complete project.
Practice quiz
Which attribute on a class-based ListView turns on pagination?
- page_size
- paginate_by
- per_page
- page_count
Answer: paginate_by. Setting paginate_by = N makes Django build the Paginator, read ?page=, and add page_obj to the context.
In DRF, what does a 401 Unauthorized response indicate?
- The user is logged in but lacks permission for the action
- The request is not authenticated — log in or send a valid token
- The server crashed while handling the request
- The client sent malformed JSON
Answer: The request is not authenticated — log in or send a valid token. 401 means authentication failed or is missing; 403 means authenticated but not allowed.
Which HTTP header does TokenAuthentication read to identify the caller?
- Authorization: Token <key>
- X-Api-Key: <key>
- Cookie: token=<key>
- Bearer: <key>
Answer: Authorization: Token <key>. DRF's TokenAuthentication expects an 'Authorization: Token <key>' header.
What does the timeout argument to cache.set() control?
- How long the database query may run
- The value's lifetime in seconds before it expires
- How many keys the cache may hold
- The maximum size of the cached value
Answer: The value's lifetime in seconds before it expires. timeout is the TTL in seconds; after it expires, cache.get() returns None (a miss).
What two things must be present for an uploaded file to reach request.FILES?
- A GET request and a CSRF token
- enctype="multipart/form-data" on the form and binding the form with request.FILES
- A FileField default and null=True
- MEDIA_URL set and DEBUG=True
Answer: enctype="multipart/form-data" on the form and binding the form with request.FILES. The form needs multipart encoding and the view must pass request.FILES when binding the form.
Which DRF permission class allows anyone to read but requires login to write?
- IsAuthenticated
- AllowAny
- IsAuthenticatedOrReadOnly
- IsAdminUser
Answer: IsAuthenticatedOrReadOnly. IsAuthenticatedOrReadOnly permits safe (read) methods for anyone but requires auth for writes.
Which method on request.session safely reads a value that might be missing?
Using .get() with a default avoids a KeyError when the key is absent.
How do you clear a user's session data on logout?
- request.session.clear_all()
- request.session.reset()
- request.session.flush()
- del request.session
Answer: request.session.flush(). flush() deletes the session data and rotates the session key; Django's logout() calls it for you.
What does throttling add to a DRF API?
- It compresses JSON responses
- It rate-limits clients, returning 429 when they exceed the limit
- It caches every view automatically
- It validates request bodies
Answer: It rate-limits clients, returning 429 when they exceed the limit. Throttling limits request rates per client and responds with 429 Too Many Requests when exceeded.
Which command audits a project's settings for production readiness?
- manage.py check --deploy
- manage.py collectstatic
- manage.py migrate --check
- manage.py runserver --prod
Answer: manage.py check --deploy. manage.py check --deploy flags insecure settings like DEBUG=True before you go live.