Chapter 14: API Development
14.1 Django REST Framework Introduction
DRF Installation Configuration
Django REST Framework (DRF) is a powerful and flexible toolkit for building Web APIs.
Installation and basic configuration:
bash
# Install DRF
pip install djangorestframework
# Optional: Install API documentation tools
pip install coreapi
pip install pyyamlpython
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'rest_framework.authtoken', # If using Token authentication
]
# DRF configuration
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
}
# If using Token authentication, need to run migrations
# python manage.py migrateSerializers
Serializers are used to convert between complex data types (like querysets and model instances) and Python native data types:
python
# serializers.py
from rest_framework import serializers
from .models import Article, Category, Tag, Comment
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']
class CommentSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(read_only=True)
class Meta:
model = Comment
fields = ['id', 'author', 'content', 'created_at']
read_only_fields = ['author', 'created_at']
class ArticleSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(read_only=True)
category = CategorySerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
comment_count = serializers.SerializerMethodField()
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'author', 'category', 'tags',
'content', 'excerpt', 'featured_image', 'status',
'view_count', 'created_at', 'updated_at', 'published_at',
'comments', 'comment_count'
]
read_only_fields = ['author', 'view_count', 'created_at', 'updated_at', 'published_at']
def get_comment_count(self, obj):
return obj.comments.filter(is_approved=True).count()
# Nested serializer example
class ArticleListSerializer(serializers.ModelSerializer):
"""Simplified serializer for list display"""
author = serializers.StringRelatedField()
category = serializers.StringRelatedField()
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'author', 'category',
'excerpt', 'featured_image', 'view_count',
'created_at', 'published_at'
]
# Custom serializer fields
class CustomArticleSerializer(serializers.ModelSerializer):
days_since_published = serializers.SerializerMethodField()
is_popular = serializers.SerializerMethodField()
class Meta:
model = Article
fields = '__all__'
read_only_fields = ['author']
def get_days_since_published(self, obj):
if obj.published_at:
from django.utils import timezone
return (timezone.now() - obj.published_at).days
return None
def get_is_popular(self, obj):
return obj.view_count > 1000API Views
DRF provides multiple ways to create API views:
python
# views.py
from rest_framework import generics, viewsets, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from .models import Article, Category, Tag
from .serializers import ArticleSerializer, ArticleListSerializer, CategorySerializer
# Function-based views
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticatedOrReadOnly])
def article_list(request):
if request.method == 'GET':
articles = Article.objects.filter(status='published')
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = ArticleSerializer(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 views
class ArticleListView(generics.ListCreateAPIView):
queryset = Article.objects.filter(status='published')
serializer_class = ArticleListSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['category', 'tags', 'author']
search_fields = ['title', 'content', 'excerpt']
ordering_fields = ['created_at', 'view_count', 'published_at']
ordering = ['-created_at']
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = 'slug'
def perform_update(self, serializer):
# Check permissions when updating
if self.request.user == serializer.instance.author:
serializer.save()
else:
raise PermissionDenied("You don't have permission to edit this article")
# ViewSet viewsets
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['category', 'tags', 'author', 'status']
search_fields = ['title', 'content', 'excerpt']
ordering_fields = ['created_at', 'view_count', 'published_at']
ordering = ['-created_at']
lookup_field = 'slug'
def get_queryset(self):
if self.action == 'list':
return Article.objects.filter(status='published')
return Article.objects.all()
def get_serializer_class(self):
if self.action == 'list':
return ArticleListSerializer
return ArticleSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
if self.request.user == serializer.instance.author or self.request.user.is_staff:
serializer.save()
else:
raise PermissionDenied("You don't have permission to edit this article")
# Custom API views
class ArticleStatsView(generics.GenericAPIView):
queryset = Article.objects.all()
def get(self, request, *args, **kwargs):
stats = {
'total_articles': Article.objects.count(),
'published_articles': Article.objects.filter(status='published').count(),
'total_views': Article.objects.aggregate(
total_views=models.Sum('view_count')
)['total_views'] or 0,
'most_popular_article': Article.objects.order_by('-view_count').first().title if Article.objects.exists() else None
}
return Response(stats)URL Routing
Configure API routing:
python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken.views import obtain_auth_token
from . import views
# Use Router to automatically register ViewSet
router = DefaultRouter()
router.register(r'articles', views.ArticleViewSet)
router.register(r'categories', views.CategoryViewSet)
router.register(r'tags', views.TagViewSet)
urlpatterns = [
# Function views
path('articles/', views.article_list, name='article-list'),
path('articles/<slug:slug>/', views.article_detail, name='article-detail'),
# Class views
path('api/articles/', views.ArticleListView.as_view(), name='api-article-list'),
path('api/articles/<slug:slug>/', views.ArticleDetailView.as_view(), name='api-article-detail'),
# ViewSet routing
path('api/v2/', include(router.urls)),
# Statistics
path('api/stats/', views.ArticleStatsView.as_view(), name='api-stats'),
# Authentication
path('api/auth/token/', obtain_auth_token, name='api-token-auth'),
# API root path
path('api/', views.api_root, name='api-root'),
]
# Custom API root view
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'articles': reverse('api-article-list', request=request, format=format),
'categories': reverse('category-list', request=request, format=format),
'tags': reverse('tag-list', request=request, format=format),
'stats': reverse('api-stats', request=request, format=format),
})Through these configurations, you have a fully functional REST API that supports authentication, permission control, filtering, search, and pagination.
Summary
Key knowledge points for Django REST Framework introduction:
- ✅ Master DRF installation and basic configuration
- ✅ Understand serializer functions and usage methods
- ✅ Learn to use different types of API views (function views, class views, ViewSet)
- ✅ Configure API routing and URL patterns
- ✅ Implement authentication and permission control
DRF provides powerful API development capabilities for Django.
Next Article
We will deeply learn RESTful API design principles.