Hello! Welcome to this advanced lesson on Django and Django REST Framework (DRF). In our last session, we introduced DRF and its core components: Serializers, ViewSets, and Routers.
Now, we'll focus on the professional features required to build a production-ready API. We'll cover how to secure your endpoints (Authentication and Permissions), how to protect your server from abuse (Throttling), how to handle large data sets (Pagination), and a popular way to handle authentication (JWT). We'll finish with a summary of all the key performance techniques in Django.
This is a critical concept. Just like in standard Django, DRF separates Authentication (who you are) from Permissions (what you're allowed to do).
Analogy: The Speakeasy
You go to a private club (an API endpoint):
In DRF, you configure these as defaults in your `settings.py` or on a per-view basis.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated, IsAdminUser
# Example of a secure view
class AdminOnlyView(APIView):
# --- Authentication ---
# "Which bouncers (methods) should we use
# to identify the user?"
authentication_classes = [TokenAuthentication]
# --- Permissions ---
# "After identifying the user, what rules must
# they pass to get into *this* room?"
permission_classes = [IsAuthenticated, IsAdminUser]
def get(self, request, format=None):
# We only get here if the user provided a valid
# token AND is an admin (is_staff=True).
return Response({'secret_data': '...'})
# --- Example of a custom "owner-only" permission ---
from rest_framework.permissions import BasePermission
class IsOwnerOrReadOnly(BasePermission):
"""
Allow any user to read (GET),
but only the owner to write (POST, PUT, DELETE).
"""
def has_object_permission(self, request, view, obj):
# Read-only methods are always allowed
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return True
# Write permissions are only allowed if
# the user *is* the object's owner
return obj.owner == request.user
Throttling (or Rate Limiting) is a feature in DRF that prevents abuse by limiting how many requests a user can make in a given period.
This is a crucial security and performance feature. It protects you from:
Analogy: The Bar
A bartender (the server) can only make so many drinks (requests) at once.
You set this in your `settings.py`. DRF handles it automatically. If a user goes over the limit, DRF stops running your view and just sends back a `429 Too Many Requests` error.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
# Limit anonymous users by IP
'rest_framework.throttling.AnonRateThrottle',
# Limit authenticated users by user_id
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
# 100 requests per day for anonymous users
'anon': '100/day',
# 1000 requests per day for authenticated users
'user': '1000/day'
}
}
Pagination is the process of splitting a very large query result into smaller "pages."
If a user asks for `/api/products/`, you should never return all 10 million products in one go. This would crash your server and the user's browser.
Instead, you paginate the results. You return only 20 products (Page 1) and provide links for the user to get Page 2, Page 3, and so on.
DRF makes this trivial. You just add a pagination class to your `settings.py`.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':
'rest_framework.pagination.PageNumberPagination',
# Show 20 items per page
'PAGE_SIZE': 20
}
Now, when a user makes a request to a `ListView` or `ViewSet`:
Request: `GET /api/products/`
Response (JSON):
{
"count": 1023,
"next": "http://api.example.com/api/products/?page=2",
"previous": null,
"results": [
{ "id": 1, "name": "Product 1" },
{ "id": 2, "name": "Product 2" },
...
{ "id": 20, "name": "Product 20" }
]
}
JWT (JSON Web Token) is a popular stateless authentication method, especially for mobile apps and JavaScript frontends.
Analogy: The Concert Wristband
This is stateless because the server doesn't need to store the token in a database (unlike sessions). The token itself contains all the info (like `user_id`) in a way that is cryptographically signed.
You implement this in DRF using a third-party package, most commonly `djangorestframework-simplejwt`.
# 1. Install the package
# pip install djangorestframework-simplejwt
# 2. settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# Use JWT as the main authentication method
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
...
}
# 3. my_project/urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
...
# This creates the '/api/token/' endpoint
path('api/token/', TokenObtainPairView.as_view()),
# This creates the '/api/token/refresh/' endpoint
path('api/token/refresh/', TokenRefreshView.as_view()),
]
# 4. How the client uses it
#
# Client sends a POST to '/api/token/' with:
# { "username": "user", "password": "pw" }
#
# Server responds with:
# {
# "refresh": "eyJ...long_refresh_token...eyJ",
# "access": "eyJ...long_access_token...eyJ"
# }
#
# Client then sends the access token in the header
# for all future requests:
# "Authorization: Bearer eyJ...long_access_token...eyJ"
This is a great "big picture" question. A slow Django app is almost always slow in one of two places: the database or the template.
Here is a checklist of the most common optimizations, in order of importance:
3 questions · no sign-up, nothing stored
Progress is stored in this browser only - no sign-up, nothing sent anywhere.
The same topic at architecture level, in the system design curriculum.