Skip to content

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 pyyaml
python
# 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 migrate

Serializers

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 > 1000

API 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:

  1. ✅ Master DRF installation and basic configuration
  2. ✅ Understand serializer functions and usage methods
  3. ✅ Learn to use different types of API views (function views, class views, ViewSet)
  4. ✅ Configure API routing and URL patterns
  5. ✅ Implement authentication and permission control

DRF provides powerful API development capabilities for Django.

Next Article

We will deeply learn RESTful API design principles.

14.2 RESTful API Design →

Directory

Return to Course Directory

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).