Django REST Framework

Django Intermediate/Advanced — Django REST Framework
Django Intermediate/Advanced
Course 2 · Chapter 2 · Django REST Framework

🔌 Django REST Framework

Chapter 3 of the last course returned JsonResponse straight out of a view — fine for one endpoint, repetitive across a whole CRUD API. Django REST Framework (DRF) is Django's answer, the same way FastAPI itself is really "Starlette plus Pydantic bundled together": DRF pairs serializers (validation in, JSON out) with viewsets and routers that generate an entire CRUD API from a handful of lines.

Installing DRF

pip install djangorestframework
# settings.py INSTALLED_APPS = [ # ... "rest_framework", "catalog", ]

Like every app, DRF does nothing until it's registered here — the same INSTALLED_APPS discipline from Chapter 1 of the last course.

📦 Serializers: Django's Pydantic Equivalent

A ModelSerializer plays exactly the role a Pydantic model plays in FastAPI — it validates incoming data and converts model instances to JSON-safe data, generated from the model the same way ModelForm generated a form:

# catalog/serializers.py from rest_framework import serializers from .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ["id", "title", "price", "in_stock"]

FastAPI's Pydantic Model vs DRF's Serializer

FastAPI: Pydantic Model
class Book(BaseModel): id: int title: str price: float in_stock: bool

Not tied to a database model — often paired with a separate SQLAlchemy model for the actual table.

DRF: ModelSerializer
class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ["id", "title", "price", "in_stock"]

Generated directly from the Django model — one less shape to keep manually in sync.

# In the Django shell — using a serializer directly from catalog.serializers import BookSerializer from catalog.models import Book book = Book.objects.first() serializer = BookSerializer(book) serializer.data # {'id': 1, 'title': 'Dune', 'price': '12.99', 'in_stock': True} # Validating incoming data (the "in" direction) serializer = BookSerializer(data={"title": "1984", "price": "9.99", "in_stock": True}) serializer.is_valid() # True — same is_valid()/errors pattern as Forms serializer.save() # creates and saves a Book, just like ModelForm.save()

API Views

The simplest DRF view is a function decorated with @api_view — like @login_required, it's a decorator adding framework behavior on top of an ordinary function:

from rest_framework.decorators import api_view from rest_framework.response import Response from .models import Book from .serializers import BookSerializer @api_view(["GET"]) def book_list(request): books = Book.objects.all() serializer = BookSerializer(books, many=True) return Response(serializer.data)

🧰 ViewSets & Routers

Writing a separate view for list/create/retrieve/update/destroy is exactly the repetition DRF exists to remove. A ModelViewSet implements all five from one class; a Router generates the matching urls.py entries automatically:

# catalog/views.py from rest_framework import viewsets from .models import Book from .serializers import BookSerializer class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
# catalog/urls.py from rest_framework.routers import DefaultRouter from .views import BookViewSet router = DefaultRouter() router.register("books", BookViewSet) urlpatterns = router.urls # One line generates all five routes: # GET /books/ -> list # POST /books/ -> create # GET /books/{id}/ -> retrieve # PUT /books/{id}/ -> update # DELETE /books/{id}/ -> destroy

Same Idea as Rails' resources

router.register("books", BookViewSet) generating 5 routes from one line is directly analogous to Rails' resources :books generating 7 — Chapter 2's hand-written urlpatterns list was the "before" picture this replaces.

queryset + serializer_class

The only two things a ModelViewSet needs to know — which rows, and how to shape them — everything else (routing to the right action, calling is_valid(), calling save()) is handled for you.

💻 Coding Challenges

Challenge 1: Write a Serializer

Write an AuthorSerializer(serializers.ModelSerializer) for the Author model, exposing id, name, and bio (but not birth_year), and show the Python code to serialize a single Author instance to a dict.

Goal: Practice writing a ModelSerializer with an explicit field allowlist.

→ Solution

Challenge 2: Build a Full CRUD API

Using the AuthorSerializer from Challenge 1, write an AuthorViewSet(viewsets.ModelViewSet) and the router registration that exposes it at /authors/.

Goal: Practice the viewset + router pattern that replaces five hand-written views and five hand-written URL patterns.

→ Solution

Challenge 3: Validate Incoming Data

Using a serializer directly (not through a view), demonstrate what serializer.is_valid() and serializer.errors return for a submitted price value that's a negative number, assuming price uses DRF's DecimalField with a min_value validator.

Goal: Practice reasoning about serializer validation independent of any view or HTTP request.

→ Solution

⚠️ Gotcha: fields = "__all__" on a Serializer Has the Same Risk as a Form

The mass-assignment danger from the last course's Forms chapter applies identically to ModelSerializer: fields = "__all__" exposes every model field — including ones like is_staff on a User-adjacent model — to both reading AND writing through the API. An API is, if anything, a more likely target than an HTML form, since it's trivial to script arbitrary JSON bodies against it. Use an explicit fields list on every serializer that touches anything sensitive, exactly as this course has insisted for ModelForm.

🎯 What's Next

With a real API in place, the next chapter covers proving it actually works: Testing Django AppsTestCase, the Django test client, and fixtures for setting up predictable test data.