Django & Flask Topic

REST APIs with Django REST Framework (DRF)

Modern applications often have:

  • A frontend (React, Vue, Angular, or a mobile app)

  • A backend (Django) that handles data and business logic

They need to communicate. APIs provide a standardized way for this communication to happen, typically using JSON data instead of HTML.

DRF makes it incredibly easy to build these APIs by providing:

  • Tools to serialize your Django models into JSON

  • View classes that handle HTTP methods (GET, POST, PUT, DELETE)

  • Authentication and permission systems

  • Browsable APIs that are self-documenting


1. What is Django REST Framework (DRF)?

DRF is a third-party package that builds on top of Django to create powerful, flexible, and standards-compliant Web APIs.

Installation and Setup

bash

pip install djangorestframework

Add it to your INSTALLED_APPS:

python

# settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework.authtoken',  # If using token auth
]

2. Key Concepts in Action

Let's build a REST API for our blog, allowing clients to Create, Read, Update, and Delete (CRUD) blog posts.

Serializers

What they are: Serializers convert complex data types (like Django model instances) into Python datatypes that can be easily rendered into JSON. They also handle deserialization, converting JSON data back into model instances.

Think of them as the bridge between your models and JSON.

Creating a Serializer:

python

# serializers.py
from rest_framework import serializers
from .models import BlogPost, Author

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name', 'email']  # Fields to include in the API

class BlogPostSerializer(serializers.ModelSerializer):
    # Nested serializer to include author details
    author = AuthorSerializer(read_only=True)
    
    class Meta:
        model = BlogPost
        fields = ['id', 'title', 'content', 'published_date', 'author', 'categories']
        read_only_fields = ['id', 'published_date']  # These are set automatically

APIView Classes

APIViews are the core of DRF. They handle HTTP methods similarly to Django views, but are specifically designed for APIs.

Basic API View:

python

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import BlogPost
from .serializers import BlogPostSerializer

class BlogPostList(APIView):
    """
    List all blog posts, or create a new one.
    """
    
    def get(self, request):
        posts = BlogPost.objects.all()
        serializer = BlogPostSerializer(posts, many=True)  # Serialize multiple objects
        return Response(serializer.data)
    
    def post(self, request):
        serializer = BlogPostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()  # This creates the new BlogPost
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Detail API View:

python

class BlogPostDetail(APIView):
    """
    Retrieve, update or delete a single blog post.
    """
    
    def get_object(self, pk):
        try:
            return BlogPost.objects.get(pk=pk)
        except BlogPost.DoesNotExist:
            return None
    
    def get(self, request, pk):
        post = self.get_object(pk)
        if post is None:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = BlogPostSerializer(post)
        return Response(serializer.data)
    
    def put(self, request, pk):
        post = self.get_object(pk)
        if post is None:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = BlogPostSerializer(post, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    def delete(self, request, pk):
        post = self.get_object(pk)
        if post is None:
            return Response(status=status.HTTP_404_NOT_FOUND)
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

URL Configuration:

python

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('posts/', views.BlogPostList.as_view(), name='post-list'),
    path('posts/<int:pk>/', views.BlogPostDetail.as_view(), name='post-detail'),
]

ViewSets and Routers

ViewSets combine the logic for multiple actions (list, create, retrieve, update, delete) into a single class. Routers automatically generate URL patterns for ViewSets.

This is the most efficient way to build APIs in DRF.

Creating a ViewSet:

python

# views.py
from rest_framework import viewsets
from .models import BlogPost
from .serializers import BlogPostSerializer

class BlogPostViewSet(viewsets.ModelViewSet):
    """
    A ViewSet for viewing and editing blog posts.
    """
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostSerializer

Using a Router:

python

# urls.py
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register(r'posts', views.BlogPostViewSet)

urlpatterns = router.urls

That's it! With just these few lines, you get a complete CRUD API with these endpoints:

  • GET/POST /posts/

  • GET/PUT/DELETE /posts/{id}/

Authentication Tokens

APIs need authentication too. While sessions work for web browsers, tokens are better for mobile apps and JavaScript frontends.

1. Token Authentication (Built-in)

python

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',  # Default to requiring auth
    ]
}

Creating tokens:

python

# When a user registers, create a token for them
from rest_framework.authtoken.models import Token

def create_user_with_token(username, password):
    user = User.objects.create_user(username=username, password=password)
    token = Token.objects.create(user=user)
    return user, token

Using tokens in API calls:

bash

# Include the token in the Authorization header
curl -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" http://localhost:8000/api/posts/

2. JWT (JSON Web Tokens) - More Modern

JWT tokens are stateless and don't require database storage.

bash

pip install djangorestframework-simplejwt

python

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ]
}

# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

Using JWT:

python

# Client gets tokens
import requests

response = requests.post('http://localhost:8000/api/token/', {
    'username': 'johndoe',
    'password': 'supersecret'
})
tokens = response.json()  # {'access': 'xxx', 'refresh': 'yyy'}

# Use the access token
headers = {'Authorization': f'Bearer {tokens["access"]}'}
response = requests.get('http://localhost:8000/api/posts/', headers=headers)

Permissions

Control who can access your API endpoints.

python

# views.py
from rest_framework import permissions

class BlogPostViewSet(viewsets.ModelViewSet):
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostSerializer
    
    def get_permissions(self):
        """
        Instantiates and returns the list of permissions that this view requires.
        """
        if self.action == 'list' or self.action == 'retrieve':
            permission_classes = [permissions.AllowAny]  # Anyone can read
        else:
            permission_classes = [permissions.IsAuthenticated]  # Only authenticated users can write
        return [permission() for permission in permission_classes]
    
    def perform_create(self, serializer):
        """Set the author to the current user when creating a post"""
        serializer.save(author=self.request.user)

Summary

Concept What it is Purpose
Serializer ModelSerializer classes Convert between Django models and JSON
APIView Class-based views with HTTP methods Handle API requests with explicit control
ViewSet ModelViewSet classes Combine CRUD operations in one class
Router DefaultRouter() Automatically generate URLs for ViewSets
Token Auth TokenAuthentication Simple token-based authentication
JWT JWTAuthentication Modern, stateless token authentication
Permissions IsAuthenticated, AllowAny Control access to API endpoints

Typical API Flow:

  1. Client sends request to /api/posts/ with JWT token in header

  2. DRF authenticates the token and checks permissions

  3. ViewSet retrieves BlogPost objects from database

  4. Serializer converts BlogPost objects to JSON

  5. DRF returns JSON response to client

Example API Output:

json

{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "title": "My First Post",
      "content": "This is the content...",
      "published_date": "2023-10-25T12:00:00Z",
      "author": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com"
      }
    }
  ]
}

DRF makes building professional-grade APIs straightforward and follows Django's "batteries-included" philosophy while being incredibly flexible.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?