Python Notes
HubHistory of Python - Complete Guide 2026Python Applications - Complete Guide 2026Python Features - Complete Guide 2026What is Python? - Complete Guide 2026Why Learn Python? - Complete Guide 2026Your First Python Program – Hello World & Beyond 2026How to Install Python on Windows, Mac & Linux – Complete Guide 2026Install VS Code for Python – Complete Setup Guide with Extensions 2026Python Environment Setup – Virtual Environments, pip & Project Structure 2026Python IDLE – Complete Guide for Beginners 2026Python CommentsPython Data TypesPython Input and OutputPython OperatorsPython SyntaxPython Strings BasicsPython Type CastingPython VariablesBreak, Continue and Pass in PythonElif Statement in PythonFor Loop in PythonIf-Else Statement in PythonIf Statement in PythonNested If in PythonPattern Programs in PythonWhile Loop in PythonDecorators in PythonFunction Arguments in PythonFunctions Basics in PythonGenerators in PythonLambda Functions in PythonRecursive Functions in PythonReturn Statement in PythonScope of Variables in PythonAbstraction in PythonClasses and Objects in PythonConstructors in PythonEncapsulation in PythonInheritance in PythonMagic Methods (Dunder Methods) in PythonMethod Overriding in PythonPolymorphism in PythonPython Comprehensions – List, Dict, Set & GeneratorPython Dictionaries – Key-Value StorePython Dictionary Methods – Complete ReferencePython List Methods – All Built-in MethodsPython Lists – Complete GuidePython Set Methods – Complete ReferencePython Sets – Unordered Unique CollectionsPython Tuples – Immutable SequencesPython Asynchronous Programming with asyncioPython Context ManagersAdvanced Python DecoratorsAdvanced Python GeneratorsPython Iterators - Advanced GuidePython Memory ManagementPython MultiprocessingPython MultithreadingCRUD Operations in Python DatabasesMySQL with Python (mysql-connector-python)ORM Introduction with SQLAlchemyPostgreSQL with Python (psycopg2)SQLite3 with PythonIntroduction to DjangoDjango Models and ORMDjango REST API with DRFDjango ViewsIntroduction to FlaskFlask RoutingFlask Templates with Jinja2Email Automation with PythonPDF Automation with PythonSelenium Web Automation with PythonTask Scheduler with PythonWeb Scraping with PythonData Analysis with PythonData Cleaning with PythonData Visualization ProjectData Visualization with MatplotlibNumPy IntroductionPandas IntroductionStatistical Visualization with SeabornClassification AlgorithmsClustering AlgorithmsIntroduction to Machine LearningMachine Learning Project: Churn PredictionModel Evaluation and SelectionRegression AlgorithmsSave pipeline (includes preprocessing + model)AI Assistant ProjectCalculator ProjectChatbot ProjectExpense Tracker ProjectFace Detection ProjectPassword Generator ProjectWeather App ProjectPython Coding Interview QuestionsPython OOP Interview Questions and AnswersPython Master CheatsheetPython Interview Questions and AnswersPython Best PracticesCommon Python Errors and SolutionsPython Debugging TechniquesPython Learning Resources
Build professional REST APIs with Django REST Framework (DRF). Learn serializers, ViewSets, routers, authentication, permissions, filtering, pagination, and testing REST APIs in Django.
Django REST Framework (DRF) is the most popular toolkit for building REST APIs in Django. It provides serializers, authentication, permissions, and viewsets that make API development fast and reliable.
Installation
bash example
pip install djangorestframework
pip install djangorestframework-simplejwt # For JWT auth
pip install django-filter # For advanced filtering
pip install drf-spectacular # For OpenAPI/Swagger docsExample
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt',
'django_filters',
'drf_spectacular',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
Serializers
Serializers convert model instances to/from JSON:
Example
# blog/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Post, Category, Comment, Tag
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name']
read_only_fields = ['id']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']
class CategorySerializer(serializers.ModelSerializer):
post_count = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description', 'post_count']
def get_post_count(self, obj):
return obj.post_set.filter(status='published').count()
class CommentSerializer(serializers.ModelSerializer):
author_username = serializers.ReadOnlyField(source='author.username')
class Meta:
model = Comment
fields = ['id', 'author_username', 'content', 'created_at']
read_only_fields = ['id', 'author_username', 'created_at']
class PostListSerializer(serializers.ModelSerializer):
"""Lighter serializer for list views."""
author = UserSerializer(read_only=True)
category_name = serializers.ReadOnlyField(source='category.name')
tags = TagSerializer(many=True, read_only=True)
comment_count = serializers.SerializerMethodField()
class Meta:
model = Post
fields = [
'id', 'title', 'slug', 'author', 'category_name',
'tags', 'excerpt', 'status', 'views_count',
'comment_count', 'created_at', 'published_at'
]
read_only_fields = ['id', 'views_count', 'created_at']
def get_comment_count(self, obj):
return obj.comments.filter(approved=True).count()
class PostDetailSerializer(serializers.ModelSerializer):
"""Full serializer for detail views."""
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True,
required=False
)
tags = TagSerializer(many=True, read_only=True)
tag_ids = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
source='tags',
many=True,
write_only=True,
required=False
)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = [
'id', 'title', 'slug', 'author', 'category', 'category_id',
'tags', 'tag_ids', 'content', 'excerpt', 'status',
'is_featured', 'views_count', 'comments', 'created_at',
'updated_at', 'published_at'
]
read_only_fields = ['id', 'slug', 'views_count', 'created_at', 'updated_at']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("Title must be at least 5 characters.")
return value
def validate(self, data):
"""Cross-field validation."""
if data.get('status') == 'published' and not data.get('excerpt'):
raise serializers.ValidationError(
{'excerpt': 'Excerpt is required when publishing a post.'}
)
return data
def create(self, validated_data):
tags = validated_data.pop('tags', [])
post = Post.objects.create(**validated_data)
post.tags.set(tags)
return post
def update(self, instance, validated_data):
tags = validated_data.pop('tags', None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if tags is not None:
instance.tags.set(tags)
return instance
API Views
Function-Based API Views
Example
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
@api_view(['GET', 'POST'])
def post_list(request):
if request.method == 'GET':
posts = Post.objects.filter(status='published')
serializer = PostListSerializer(posts, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = PostDetailSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Class-Based API Views
python example
from rest_framework.views import APIView
from rest_framework.generics import (
ListCreateAPIView, RetrieveUpdateDestroyAPIView,
ListAPIView, RetrieveAPIView
)
class PostListCreateView(ListCreateAPIView):
serializer_class = PostListSerializer
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class PostRetrieveUpdateDestroyView(RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
lookup_field = 'slug'
def get_serializer_class(self):
if self.request.method == 'GET':
return PostDetailSerializer
return PostDetailSerializer
def perform_update(self, serializer):
serializer.save()
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.author != request.user:
return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)ViewSets and Routers
ViewSets combine CRUD logic into a single class:
Example
# blog/views.py
from rest_framework import viewsets, permissions, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
class PostViewSet(viewsets.ModelViewSet):
"""
Full CRUD API for blog posts.
list: GET /api/posts/
create: POST /api/posts/
retrieve: GET /api/posts/{slug}/
update: PUT /api/posts/{slug}/
partial_update: PATCH /api/posts/{slug}/
destroy: DELETE /api/posts/{slug}/
"""
queryset = Post.objects.all().select_related('author', 'category').prefetch_related('tags')
lookup_field = 'slug'
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['status', 'category', 'author', 'is_featured']
search_fields = ['title', 'content', 'author__username']
ordering_fields = ['created_at', 'views_count', 'title']
ordering = ['-created_at']
def get_serializer_class(self):
if self.action == 'list':
return PostListSerializer
return PostDetailSerializer
def get_permissions(self):
if self.action in ['list', 'retrieve']:
return [permissions.AllowAny()]
return [permissions.IsAuthenticated()]
def get_queryset(self):
queryset = super().get_queryset()
if self.action == 'list':
queryset = queryset.filter(status='published')
return queryset
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
if serializer.instance.author != self.request.user:
raise PermissionError("You can only edit your own posts.")
serializer.save()
# Custom actions
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
def publish(self, request, slug=None):
"""Publish a draft post. POST /api/posts/{slug}/publish/"""
post = self.get_object()
if post.author != request.user:
return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)
post.status = 'published'
from django.utils import timezone
post.published_at = timezone.now()
post.save()
return Response({'message': 'Post published successfully', 'status': post.status})
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
def like(self, request, slug=None):
"""Like a post. POST /api/posts/{slug}/like/"""
post = self.get_object()
user = request.user
if user in post.liked_by.all():
post.liked_by.remove(user)
return Response({'liked': False, 'likes': post.liked_by.count()})
else:
post.liked_by.add(user)
return Response({'liked': True, 'likes': post.liked_by.count()})
@action(detail=False, methods=['get'], permission_classes=[permissions.IsAuthenticated])
def my_posts(self, request):
"""Get current user's posts. GET /api/posts/my_posts/"""
posts = Post.objects.filter(author=request.user)
page = self.paginate_queryset(posts)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(posts, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def featured(self, request):
"""Get featured posts. GET /api/posts/featured/"""
featured_posts = Post.objects.filter(status='published', is_featured=True)
serializer = PostListSerializer(featured_posts, many=True)
return Response(serializer.data)
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
"""Read-only API for categories."""
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'slug'
@action(detail=True, methods=['get'])
def posts(self, request, slug=None):
"""Get posts for a category. GET /api/categories/{slug}/posts/"""
category = self.get_object()
posts = Post.objects.filter(category=category, status='published')
serializer = PostListSerializer(posts, many=True)
return Response(serializer.data)
URL Router Configuration
Example
# blog/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'posts', views.PostViewSet, basename='post')
router.register(r'categories', views.CategoryViewSet, basename='category')
urlpatterns = [
path('api/', include(router.urls)),
]
# Generated routes:
# GET /api/posts/ → PostViewSet.list
# POST /api/posts/ → PostViewSet.create
# GET /api/posts/{slug}/ → PostViewSet.retrieve
# PUT /api/posts/{slug}/ → PostViewSet.update
# PATCH /api/posts/{slug}/ → PostViewSet.partial_update
# DELETE /api/posts/{slug}/ → PostViewSet.destroy
# POST /api/posts/{slug}/publish/ → PostViewSet.publish
# POST /api/posts/{slug}/like/ → PostViewSet.like
# GET /api/posts/my_posts/ → PostViewSet.my_posts
# GET /api/posts/featured/ → PostViewSet.featured
Authentication
JWT Authentication
python example
# settings.py
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'AUTH_HEADER_TYPES': ('Bearer',),
}Example
# accounts/urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
# Login: POST /api/token/ → {"username": "...", "password": "..."}
# Returns: {"access": "...", "refresh": "..."}
# Refresh: POST /api/token/refresh/ → {"refresh": "..."}
Custom Permissions
python example
# blog/permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsOwnerOrReadOnly(BasePermission):
"""Allow owners to edit, everyone to read."""
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.author == request.user
class IsAdminOrReadOnly(BasePermission):
"""Allow admins to write, everyone to read."""
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
return request.user and request.user.is_staff
class IsVerifiedUser(BasePermission):
"""Only allow verified users."""
message = 'Please verify your email address.'
def has_permission(self, request, view):
return (
request.user and
request.user.is_authenticated and
getattr(request.user, 'email_verified', False)
)API Testing
Example
# blog/tests.py
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from django.contrib.auth.models import User
from .models import Post, Category
class PostAPITests(APITestCase):
def setUp(self):
# Create test user
self.user = User.objects.create_user(
username='testuser',
password='testpass123',
email='test@test.com'
)
self.client = APIClient()
# Create test category
self.category = Category.objects.create(name='Tech', slug='tech')
# Create test post
self.post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
content='Test content',
status='published'
)
def test_list_posts_unauthenticated(self):
"""Anyone can list published posts."""
response = self.client.get('/api/posts/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 1)
def test_create_post_authenticated(self):
"""Authenticated users can create posts."""
self.client.force_authenticate(user=self.user)
data = {
'title': 'New Post',
'content': 'New content here',
'excerpt': 'Summary',
'status': 'published',
'category_id': self.category.id
}
response = self.client.post('/api/posts/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Post.objects.count(), 2)
def test_create_post_unauthenticated(self):
"""Unauthenticated users cannot create posts."""
data = {'title': 'Unauthorized Post', 'content': 'Content'}
response = self.client.post('/api/posts/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_update_own_post(self):
"""Users can update their own posts."""
self.client.force_authenticate(user=self.user)
data = {'title': 'Updated Title', 'content': 'Updated content'}
response = self.client.patch(f'/api/posts/{self.post.slug}/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.post.refresh_from_db()
self.assertEqual(self.post.title, 'Updated Title')
def test_delete_post(self):
"""Users can delete their own posts."""
self.client.force_authenticate(user=self.user)
response = self.client.delete(f'/api/posts/{self.post.slug}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Post.objects.count(), 0)
# Run tests:
# python manage.py test blog
Summary
In this lesson, you learned:
- ✅ Installing and configuring Django REST Framework
- ✅ Creating ModelSerializers with validation
- ✅ Function-based and class-based API views
- ✅ ViewSets with full CRUD and custom actions
- ✅ URL routing with DRF Router
- ✅ JWT authentication setup
- ✅ Custom permission classes
- ✅ Writing API tests with APITestCase
*Next Section: Data Science →*
Continue the lesson path
Study next in this module
01Web DevelopmentDjango Models and ORMMaster Django models and the ORM. Learn to define database models, field types, relationships (ForeignKey, ManyToMany, OneToOne), migrations, QuerySets, and advanced database queries in Django.02Web DevelopmentDjango ViewsMaster Django views — function-based views (FBVs), class-based views (CBVs), generic views, mixins, authentication, form handling, and REST-style view patterns with full practical examples.03Web DevelopmentIntroduction to DjangoLearn Django, Python04Web DevelopmentIntroduction to FlaskLearn Flask, the lightweight Python web framework. Build your first web application, understand Flask