Django REST Framework 全面指南:从模型到完整API接口开发

1. Django REST Framework 简介

Django REST Framework(简称DRF)是一个强大而灵活的工具包,用于在Django中构建Web API。它提供了一系列工具和库,帮助我们快速开发符合RESTful风格的API接口。DRF的核心优势在于其简洁的代码结构、强大的序列化功能、丰富的视图类以及可扩展的认证权限系统。

在开始之前,确保已经安装DRF:

bash 复制代码
pip install djangorestframework

2. 模型定义与设计

让我们以一个简单的博客文章模型为例,展示完整的API开发流程。首先,在models.py中定义我们的Article模型:

python 复制代码
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class Article(models.Model):
    # 文章状态选择
    STATUS_CHOICES = (
        ('draft', '草稿'),
        ('published', '已发布'),
        ('archived', '已归档'),
    )
    
    # 基础字段
    title = models.CharField(max_length=200, verbose_name='标题')
    slug = models.SlugField(max_length=200, unique=True, verbose_name='URL标识')
    content = models.TextField(verbose_name='内容')
    excerpt = models.TextField(max_length=500, blank=True, verbose_name='摘要')
    
    # 关系字段
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE, 
        related_name='articles',
        verbose_name='作者'
    )
    
    # 状态与时间字段
    status = models.CharField(
        max_length=10, 
        choices=STATUS_CHOICES, 
        default='draft',
        verbose_name='状态'
    )
    publish_time = models.DateTimeField(
        default=timezone.now, 
        verbose_name='发布时间'
    )
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    
    # 数字字段
    view_count = models.PositiveIntegerField(default=0, verbose_name='浏览次数')
    like_count = models.PositiveIntegerField(default=0, verbose_name='点赞数')
    
    # 标记字段
    is_featured = models.BooleanField(default=False, verbose_name='是否推荐')
    
    class Meta:
        db_table = 'blog_articles'
        verbose_name = '文章'
        verbose_name_plural = '文章'
        ordering = ['-publish_time', '-created_time']
        indexes = [
            models.Index(fields=['status', 'publish_time']),
            models.Index(fields=['author', 'created_time']),
        ]
    
    def __str__(self):
        return self.title
    
    def save(self, *args, **kwargs):
        # 自动生成摘要(如果未提供)
        if not self.excerpt and self.content:
            self.excerpt = self.content[:150] + '...' if len(self.content) > 150 else self.content
        super().save(*args, **kwargs)

这个模型设计考虑了实际应用场景,包含了文章管理所需的各类字段,为后续的API开发奠定了良好的数据基础。

3. 序列化器深度解析

序列化器是DRF的核心组件之一,负责数据的序列化(Python对象→JSON)和反序列化(JSON→Python对象)。让我们创建详细的序列化器:

python 复制代码
from rest_framework import serializers
from .models import Article
from django.contrib.auth.models import User
from django.utils import timezone

class UserSerializer(serializers.ModelSerializer):
    """用户序列化器,用于嵌套显示作者信息"""
    full_name = serializers.CharField(source='get_full_name', read_only=True)
    
    class Meta:
        model = User
        fields = ['id', 'username', 'full_name', 'email']
        read_only_fields = ['id', 'username', 'full_name', 'email']

class ArticleSerializer(serializers.ModelSerializer):
    """文章序列化器 - 完整版本"""
    
    # 只读字段 - 显示详细信息
    author_info = UserSerializer(source='author', read_only=True)
    status_display = serializers.CharField(source='get_status_display', read_only=True)
    days_since_published = serializers.SerializerMethodField(read_only=True)
    
    # 可写字段 - 用于创建和更新
    author = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(),
        write_only=True,
        required=False  # 在创建时可以通过上下文自动设置
    )
    
    class Meta:
        model = Article
        fields = [
            'id', 'title', 'slug', 'content', 'excerpt',
            'author', 'author_info', 'status', 'status_display',
            'publish_time', 'created_time', 'updated_time',
            'view_count', 'like_count', 'is_featured',
            'days_since_published'
        ]
        read_only_fields = [
            'id', 'author_info', 'created_time', 'updated_time',
            'view_count', 'like_count', 'days_since_published'
        ]
        extra_kwargs = {
            'slug': {'required': False},
            'content': {'write_only': False},
        }
    
    def get_days_since_published(self, obj):
        """计算文章发布至今的天数"""
        if obj.publish_time and obj.status == 'published':
            delta = timezone.now() - obj.publish_time
            return delta.days
        return None
    
    def validate_title(self, value):
        """标题验证"""
        if len(value.strip()) < 5:
            raise serializers.ValidationError("标题长度不能少于5个字符")
        if len(value) > 200:
            raise serializers.ValidationError("标题长度不能超过200个字符")
        return value.strip()
    
    def validate_slug(self, value):
        """Slug验证"""
        if value:
            import re
            if not re.match(r'^[a-z0-9-]+$', value):
                raise serializers.ValidationError("Slug只能包含小写字母、数字和连字符")
            
            # 检查slug是否唯一(排除当前实例)
            instance = self.instance
            if instance and instance.slug == value:
                return value
                
            if Article.objects.filter(slug=value).exists():
                raise serializers.ValidationError("该URL标识已被使用")
        return value
    
    def validate(self, attrs):
        """全局验证"""
        # 确保草稿状态的文章没有发布时间
        status = attrs.get('status', self.instance.status if self.instance else 'draft')
        publish_time = attrs.get('publish_time')
        
        if status == 'draft' and publish_time:
            raise serializers.ValidationError({
                'publish_time': '草稿状态的文章不能设置发布时间'
            })
        
        # 自动生成slug(如果未提供)
        if not attrs.get('slug') and attrs.get('title'):
            from django.utils.text import slugify
            attrs['slug'] = slugify(attrs['title'])
        
        return attrs
    
    def create(self, validated_data):
        """创建文章时的额外处理"""
        # 如果没有指定作者,使用当前用户
        if 'author' not in validated_data:
            request = self.context.get('request')
            if request and hasattr(request, 'user'):
                validated_data['author'] = request.user
        
        return super().create(validated_data)
    
    def update(self, instance, validated_data):
        """更新文章时的额外处理"""
        # 记录内容变更
        if 'content' in validated_data and instance.content != validated_data['content']:
            # 这里可以添加内容变更日志
            pass
            
        return super().update(instance, validated_data)

class ArticleListSerializer(serializers.ModelSerializer):
    """文章列表序列化器 - 简化版本,用于列表展示"""
    author_name = serializers.CharField(source='author.get_full_name', read_only=True)
    excerpt = serializers.CharField(read_only=True)
    comment_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Article
        fields = [
            'id', 'title', 'slug', 'excerpt', 'author_name',
            'publish_time', 'view_count', 'like_count', 
            'is_featured', 'status', 'comment_count'
        ]
        read_only_fields = fields
    
    def get_comment_count(self, obj):
        # 假设有评论模型,这里返回评论数量
        return 0  # 实际项目中这里应该返回真实的评论数

这个序列化器设计展示了DRF的强大功能,包括字段级别的验证、全局验证、自定义方法字段、嵌套序列化器等高级特性。

4. 视图集与视图类详细实现

DRF提供了多种视图类,从基础的APIView到功能完整的ViewSet,让我们详细实现:

python 复制代码
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly, BasePermission
from django_filters.rest_framework import DjangoFilterBackend
from .models import Article
from .serializers import ArticleSerializer, ArticleListSerializer

class IsOwnerOrReadOnly(BasePermission):
    """
    自定义权限:只允许文章的所有者编辑,其他用户只能查看
    """
    def has_object_permission(self, request, view, obj):
        # 读取权限允许任何请求
        if request.method in ['GET', 'HEAD', 'OPTIONS']:
            return True
        
        # 写入权限只允许文章作者
        return obj.author == request.user

class ArticleViewSet(viewsets.ModelViewSet):
    """
    文章视图集
    提供列表、创建、检索、更新、删除等完整操作
    """
    queryset = Article.objects.all()
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    
    # 过滤字段
    filterset_fields = ['status', 'author', 'is_featured']
    
    # 搜索字段
    search_fields = ['title', 'content', 'excerpt']
    
    # 排序字段
    ordering_fields = ['publish_time', 'created_time', 'view_count', 'like_count']
    ordering = ['-publish_time']
    
    def get_serializer_class(self):
        """
        根据动作选择不同的序列化器
        """
        if self.action == 'list':
            return ArticleListSerializer
        return ArticleSerializer
    
    def get_queryset(self):
        """
        根据用户权限返回不同的查询集
        """
        queryset = super().get_queryset()
        
        # 未认证用户只能查看已发布的文章
        if not self.request.user.is_authenticated:
            return queryset.filter(status='published')
        
        # 普通用户可以看到自己所有的文章和已发布的文章
        if not self.request.user.is_staff:
            return queryset.filter(
                models.Q(status='published') | 
                models.Q(author=self.request.user)
            )
        
        # 管理员可以看到所有文章
        return queryset
    
    def perform_create(self, serializer):
        """
        创建文章时的额外操作
        """
        # 自动设置作者为当前用户
        serializer.save(author=self.request.user)
        
        # 记录创建日志
        # logger.info(f"User {self.request.user} created article: {serializer.data['title']}")
    
    def perform_update(self, serializer):
        """
        更新文章时的额外操作
        """
        instance = serializer.save()
        
        # 记录更新日志
        # logger.info(f"User {self.request.user} updated article: {instance.title}")
    
    def perform_destroy(self, instance):
        """
        删除文章时的额外操作
        """
        # 记录删除日志
        # logger.info(f"User {self.request.user} deleted article: {instance.title}")
        
        instance.delete()
    
    @action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
    def like(self, request, pk=None):
        """
        点赞文章
        """
        article = self.get_object()
        user = request.user
        
        # 这里可以添加喜欢逻辑,比如使用Through模型记录喜欢关系
        # 当前简单实现:直接增加计数
        
        article.like_count += 1
        article.save()
        
        return Response({
            'status': 'success',
            'like_count': article.like_count,
            'message': '点赞成功'
        })
    
    @action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
    def publish(self, request, pk=None):
        """
        发布文章(将状态改为published)
        """
        article = self.get_object()
        
        # 检查权限
        if article.author != request.user and not request.user.is_staff:
            return Response(
                {'error': '没有权限发布此文章'}, 
                status=status.HTTP_403_FORBIDDEN
            )
        
        article.status = 'published'
        if not article.publish_time:
            article.publish_time = timezone.now()
        article.save()
        
        serializer = self.get_serializer(article)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
    def my_articles(self, request):
        """
        获取当前用户的文章
        """
        articles = self.get_queryset().filter(author=request.user)
        
        page = self.paginate_queryset(articles)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        
        serializer = self.get_serializer(articles, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def featured(self, request):
        """
        获取推荐文章
        """
        featured_articles = self.get_queryset().filter(
            is_featured=True, 
            status='published'
        )
        
        page = self.paginate_queryset(featured_articles)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        
        serializer = self.get_serializer(featured_articles, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['get'])
    def statistics(self, request, pk=None):
        """
        获取文章统计信息
        """
        article = self.get_object()
        
        return Response({
            'id': article.id,
            'title': article.title,
            'view_count': article.view_count,
            'like_count': article.like_count,
            'created_time': article.created_time,
            'days_online': (timezone.now() - article.created_time).days if article.created_time else 0
        })

5. 路由配置详解

urls.py中配置路由:

python 复制代码
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet

# 创建路由器并注册视图集
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')

# API URL模式
urlpatterns = [
    path('api/', include(router.urls)),
    path('api-auth/', include('rest_framework.urls')),  # DRF的登录视图
]

# 可选:添加自定义API文档
from rest_framework.documentation import include_docs_urls
urlpatterns += [
    path('api/docs/', include_docs_urls(title='Blog API Documentation')),
]

6. 分页与过滤配置

settings.py中配置全局分页和过滤:

python 复制代码
REST_FRAMEWORK = {
    '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_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
    
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
}

7. 完整API接口测试

现在我们已经完成了完整的API开发,可以通过以下方式测试:

获取文章列表

bash 复制代码
GET /api/articles/

创建新文章

bash 复制代码
POST /api/articles/
{
    "title": "我的第一篇文章",
    "content": "这是文章内容...",
    "status": "draft"
}

获取单篇文章

python 复制代码
GET /api/articles/1/

更新文章

python 复制代码
PUT /api/articles/1/
{
    "title": "更新后的标题",
    "content": "更新后的内容...",
    "status": "published"
}

删除文章

python 复制代码
DELETE /api/articles/1/

自定义动作

python 复制代码
POST /api/articles/1/like/      # 点赞
POST /api/articles/1/publish/   # 发布文章
GET /api/articles/my_articles/  # 我的文章
GET /api/articles/featured/     # 推荐文章
GET /api/articles/1/statistics/ # 文章统计

8. 高级特性与最佳实践

8.1 信号处理

python 复制代码
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from .models import Article

@receiver(pre_save, sender=Article)
def update_article_slug(sender, instance, **kwargs):
    """自动更新slug"""
    if not instance.slug:
        from django.utils.text import slugify
        instance.slug = slugify(instance.title)

@receiver(post_save, sender=Article)
def log_article_activity(sender, instance, created, **kwargs):
    """记录文章活动日志"""
    action = 'created' if created else 'updated'
    # 记录到日志系统或活动流

8.2 自定义异常处理

python 复制代码
from rest_framework.views import exception_handler
from rest_framework.response import Response

def custom_exception_handler(exc, context):
    """自定义异常处理"""
    response = exception_handler(exc, context)
    
    if response is not None:
        response.data = {
            'error': {
                'code': response.status_code,
                'message': response.data.get('detail', '发生错误'),
                'details': response.data
            }
        }
    
    return response

8.3 性能优化

python 复制代码
class ArticleViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        """使用select_related和prefetch_related优化查询"""
        return Article.objects.select_related('author').prefetch_related('tags')

9. 总结

通过这个完整的示例,我们展示了如何使用Django REST Framework开发功能丰富的API接口。主要涵盖:

  1. 模型设计:创建具有实际业务意义的数据库模型

  2. 序列化器:实现复杂的数据验证和转换逻辑

  3. 视图集:利用DRF的ViewSet提供完整的CRUD操作

  4. 权限控制:实现细粒度的访问控制

  5. 过滤搜索:提供灵活的数据查询能力

  6. 自定义动作:扩展标准REST接口

  7. 路由配置:自动生成API端点

  8. 最佳实践:包括异常处理、性能优化等

DRF的强大之处在于其模块化设计和丰富的功能,使得开发者可以快速构建出符合生产标准的API接口。通过合理使用DRF提供的各种组件,我们可以创建出既强大又易于维护的Web API。

相关推荐
长栎8 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode8 小时前
Redis 在生产项目的使用
前端·后端
用户559822481228 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode8 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战8 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha9 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn9 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425919 小时前
ShardingJDBC
后端
行者全栈架构师9 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改9 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构