Hello! We've covered a lot of Django and DRF theory in the previous lessons. Now it's time to put it all together. This lesson focuses on practical, scenario-based questions.
These are the "how-to" questions that test whether you can actually build and maintain a real-world application. We'll cover the high-level architecture of deployment, walk through common tasks like adding login and optimizing a query, and finish by building a mini-API from scratch. This is a great way to connect all the concepts we've learned.
This is a high-level system design question. You should never use `python manage.py runserver` in production. That is a single-threaded, insecure server meant only for development.
A professional deployment has three main components:
Analogy: A High-End Restaurant
Simplified Flow:
`User` → `NGINX` → `Gunicorn` → `Django`
What about AWS?
AWS (or Heroku, DigitalOcean, etc.) is the cloud provider that gives you the virtual servers (e.g., EC2 instances) to run NGINX and Gunicorn, plus the managed services for your database (e.g., AWS RDS for PostgreSQL) and file storage (e.g., AWS S3 for media files).
This is a practical test of your knowledge of Django's "batteries-included" auth system. The key is to use Django's built-in forms and views.
Here are the 4 steps:
# 1. my_project/urls.py
# Import the built-in auth views
from django.contrib.auth import views as auth_views
urlpatterns = [
...
# Django provides the view logic for us
path('login/', auth_views.LoginView.as_view(
template_name='registration/login.html'
), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
# 2. templates/registration/login.html
# Django looks for this specific path by default.
"""
<form method="post">
{% csrf_token %}
{{ form.as_p }} <button type="submit">Login</button>
</form>
"""
# 3. my_project/settings.py
# After login, Django will redirect the user to the homepage.
LOGIN_REDIRECT_URL = '/'
# After logout, it will also go to the homepage.
LOGOUT_REDIRECT_URL = '/'
# 4. templates/base.html (links)
"""
{% if user.is_authenticated %}
Hi {{ user.username }}!
<a href="{% url 'logout' %}">Log Out</a>
{% else %}
<a href="{% url 'login' %}">Log In</a>
{% endif %}
"""
Tip: The key is to show you don't need to write your own view or form. Django's `LoginView` automatically uses its built-in `AuthenticationForm` and handles all the logic of checking the password and setting the session cookie.
This is the N+1 query problem, which we covered in a previous lesson. This is the most common and important optimization scenario.
The Scenario: You have a `ListView` for `Posts`, and in your template, you show the author's name for each post. The page is making 51 queries.
The Fix: You must use `select_related` to fetch the related `Author` in the same query as the posts.
# models.py
class Author(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# --------------------------------------------------
# SLOW: The N+1 Problem
# --------------------------------------------------
# views.py (BAD)
class PostListView(ListView):
model = Post # This is just: Post.objects.all()
# template.html (BAD)
"""
{% for post in object_list %} <p>
{{ post.title }} by
{{ post.author.name }} </p>
{% endfor %}
"""
# --------------------------------------------------
# FAST: The Optimized Solution
# --------------------------------------------------
# views.py (GOOD)
class PostListView(ListView):
model = Post
def get_queryset(self):
# We override get_queryset to add the optimization.
# This tells the ORM to "follow the ForeignKey"
# and get the author data using a SQL JOIN.
return Post.objects.select_related('author')
# template.html (GOOD)
# The template code doesn't change at all!
"""
{% for post in object_list %}
<p>
{{ post.title }} by
{{ post.author.name }} </p>
{% endfor %}
"""
Key Takeaway: The fix is not in the template. The fix is in the view's queryset, where you use `select_related` (for `ForeignKey`/`OneToOne`) or `prefetch_related` (for `ManyToMany`/reverse `ForeignKey`) to fetch all the data in a minimal number of queries.
This scenario combines knowledge of models, forms, and views. The key parts are:
For this scenario, let's add validation to only allow PDF files under 2MB.
# 1. my_app/models.py
class UserReport(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
report_file = models.FileField(upload_to='reports/')
# 2. my_app/forms.py
from django import forms
from .models import UserReport
class ReportForm(forms.ModelForm):
class Meta:
model = UserReport
fields = ['report_file']
# --- This is the validation step ---
def clean_report_file(self):
file = self.cleaned_data.get('report_file')
if file:
# Check file size (e.g., 2MB limit)
if file.size > 2 * 1024 * 1024:
raise forms.ValidationError("File is too large (max 2MB).")
# Check file type
if not file.name.endswith('.pdf'):
raise forms.ValidationError("Only PDF files are allowed.")
return file
# 3. my_app/views.py
def upload_report_view(request):
if request.method == 'POST':
form = ReportForm(request.POST, request.FILES)
if form.is_valid():
# We add the user manually before saving
report = form.save(commit=False)
report.user = request.user
report.save()
return redirect('dashboard')
else:
form = ReportForm()
return render(request, 'upload_report.html', {'form': form})
This scenario tests if you can combine all the DRF concepts (Model, Serializer, ViewSet, Router) to build a complete CRUD (Create, Read, Update, Delete) API.
We will build a read-write API for an `Employee` model in just 4 steps.
# 1. models.py (The data structure)
from django.db import models
class Employee(models.Model):
name = models.CharField(max_length=100)
department = models.CharField(max_length=50)
salary = models.IntegerField()
def __str__(self):
return self.name
# 2. serializers.py (The data translator)
from rest_framework import serializers
from .models import Employee
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = ['id', 'name', 'department', 'salary']
# 3. views.py (The logic)
from rest_framework import viewsets
from .models import Employee
from .serializers import EmployeeSerializer
class EmployeeViewSet(viewsets.ModelViewSet):
"""
This one class provides all CRUD endpoints:
- GET /employees/ (list)
- POST /employees/ (create)
- GET /employees/{id}/ (retrieve)
- PUT /employees/{id}/ (update)
- DELETE /employees/{id}/ (destroy)
"""
queryset = Employee.objects.all()
serializer_class = EmployeeSerializer
# We could add permissions here, e.g.:
# permission_classes = [permissions.IsAdminUser]
# 4. urls.py (The wiring)
from rest_framework.routers import DefaultRouter
from my_app import views
router = DefaultRouter()
router.register(r'employees', views.EmployeeViewSet)
# The API URLs are now available under /
urlpatterns = router.urls
By writing these four small blocks of code, you have created a fully functional, browsable API for your `Employee` model.
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.