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。

相关推荐
K2I-3 小时前
UCI中Steel Plates Faults不平衡数据集处理
python
鸽鸽程序猿3 小时前
【项目】基于Spring全家桶的论坛系统 【下】
后端·spring·restful
IT_陈寒3 小时前
Redis性能优化:5个被低估的配置项让你的QPS提升50%
前端·人工智能·后端
radient3 小时前
初识Agent、Prompt、Function Coding、MCP
后端·程序员·架构
Lisonseekpan3 小时前
Spring Boot 中使用 Caffeine 缓存详解与案例
java·spring boot·后端·spring·缓存
SimonKing3 小时前
分布式日志排查太头疼?TLog 让你一眼看穿请求链路!
java·后端·程序员
感谢地心引力3 小时前
【Python】基于 PyQt6 和 Conda 的 PyInstaller 打包工具
数据库·python·conda·pyqt·pyinstaller
小许学java3 小时前
Spring AI快速入门以及项目的创建
java·开发语言·人工智能·后端·spring·ai编程·spring ai
*长铗归来*4 小时前
ASP.NET Core Web API 中控制器操作的返回类型及Swagger
后端·c#·asp.net·.netcore