Hello! This lesson is all about practical application. We've covered the theory of Django, DRF, and databases. Now, we'll walk through five common scenarios that you will absolutely encounter as a professional developer.
We'll cover how to implement JWT authentication, outline the steps for a production deployment on AWS, create a debugging checklist, fix the dreaded N+1 query problem, and build a custom middleware from scratch. These questions are designed to show an interviewer that you can not only talk about Django, but you can use it to solve real problems.
This is a very common task for modern web apps, especially those with separate JavaScript frontends or mobile apps. JWT (JSON Web Token) is a stateless authentication method.
Analogy: The Concert Wristband
Instead of a session (a coat-check ticket that the server has to look up in a database), JWT is like a tamper-proof wristband. The server gives it to you at login. It contains your `user_id` inside it, and it's cryptographically signed. To access a secure page, you just show the wristband. The server can verify the signature without ever looking in the database.
The most popular library for this is `djangorestframework-simplejwt`. Here is the step-by-step setup.
# 1. Install the library
# pip install djangorestframework-simplejwt
# 2. Add it to settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt',
...
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# This tells DRF to use JWT for authentication
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
...
}
# 3. Add the token URLs to my_project/urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
...
# This URL is where users POST their
# username/password to get a token
path('api/token/',
TokenObtainPairView.as_view(),
name='token_obtain_pair'),
# This URL is used to get a new access token
# using a valid refresh token
path('api/token/refresh/',
TokenRefreshView.as_view(),
name='token_refresh'),
]
# 4. Secure your views
# Now, any DRF view that uses 'IsAuthenticated'
# will automatically require a valid JWT.
# my_app/views.py
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
class MySecureView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
# This code will only run if the user provides a
# valid token in the 'Authorization: Bearer ...' header.
return Response({'message': f'Hello, {request.user.username}!'})
This is a high-level system design question. The interviewer wants to see that you understand the different components of a scalable, production deployment.
Analogy: The Restaurant Chain
You don't build your restaurant, kitchen, and warehouse all in one giant, flammable building. You use specialized services.
Here is the standard architecture:
Deployment Checklist:
1. Set `DEBUG = False` and configure `SECRET_KEY` from environment variables.
2. Set up `django-storages` to point `MEDIA_ROOT` to your S3 bucket.
3. Run `python manage.py collectstatic` to gather all static files.
4. Upload these static files to an S3 bucket (or serve with NGINX).
5. Configure NGINX to serve static files and proxy other requests to Gunicorn.
6. Run Gunicorn: `gunicorn my_project.wsgi:application --bind 0.0.0.0:8000`.
This is a common "panic" scenario. The key is to have a calm, logical checklist. The problem is almost always a configuration or path issue.
Analogy: Your car won't start. You don't immediately rebuild the engine. You check the simple things first: "Is there gas?" "Is the battery connected?"
Here is the debugging checklist, from most to least likely:
This is the exact same question as #21, but phrased as a practical scenario. This shows how important this one concept is.
The Scenario: You have a `ListView` for `Books`. The template shows each `Book`'s `Author` (a `ForeignKey`) and its `Toppings` (a `ManyToManyField`). The Django Debug Toolbar shows 102 queries for 10 books.
The Fix: You must use both `select_related` and `prefetch_related` to fetch all data in just three queries.
# models.py
class Author(models.Model):
name = models.CharField(max_length=100)
class Topping(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
toppings = models.ManyToManyField(Topping)
# views.py (The Fix)
class BookListView(ListView):
model = Book
def get_queryset(self):
# 1. (Query 1) Get all Books, JOINING Author data
# because it's a ForeignKey
qs = Book.objects.select_related('author')
# 2. (Query 2 & 3) Do one extra lookup for all
# toppings and join them in Python.
qs = qs.prefetch_related('toppings')
return qs
# template.html
"""
{% for book in object_list %}
<h3>{{ book.title }}</h3>
<p>By {{ book.author.name }}</p>
<ul>
{% for topping in book.toppings.all %}
<li>{{ topping.name }}</li>
{% endfor %}
</ul>
{% endfor %}
"""
This tests your understanding of the request/response cycle. We want to log the `path`, `method`, and `user` for every single request that hits our server.
Middleware is a class (or function) that has access to the `request` before it hits the view, and the `response` after it leaves the view.
The simplest way is to create a class with an `__init__` and a `__call__` method.
# my_app/middleware.py
import logging
# Get a logger instance
logger = logging.getLogger(__name__)
class RequestLogMiddleware:
def __init__(self, get_response):
# This is boilerplate: run once on server start
self.get_response = get_response
def __call__(self, request):
# --- Code here runs BEFORE the view ---
# Get user, default to 'Anonymous'
user = 'Anonymous'
if request.user.is_authenticated:
user = request.user.username
# This is the "request" part of the log
logger.info(
f"Request: {request.method} {request.path} by {user}"
)
# This line is critical: it calls the next
# middleware, or the view itself
response = self.get_response(request)
# --- Code here runs AFTER the view ---
# This is the "response" part of the log
logger.info(
f"Response: {response.status_code} for {request.path}"
)
return response
# settings.py
# Finally, we must install our new middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# Add our custom middleware here.
# Order can matter. Place it after auth/session
# if you need access to request.user.
'my_app.middleware.RequestLogMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
Sample Output (in your server log):
INFO:my_app.middleware:Request: GET /admin/ by admin
INFO:my_app.middleware:Response: 200 for /admin/
INFO:my_app.middleware:Request: GET /api/products/ by Anonymous
INFO:my_app.middleware:Response: 200 for /api/products/
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.