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接口。主要涵盖:
-
模型设计:创建具有实际业务意义的数据库模型
-
序列化器:实现复杂的数据验证和转换逻辑
-
视图集:利用DRF的ViewSet提供完整的CRUD操作
-
权限控制:实现细粒度的访问控制
-
过滤搜索:提供灵活的数据查询能力
-
自定义动作:扩展标准REST接口
-
路由配置:自动生成API端点
-
最佳实践:包括异常处理、性能优化等
DRF的强大之处在于其模块化设计和丰富的功能,使得开发者可以快速构建出符合生产标准的API接口。通过合理使用DRF提供的各种组件,我们可以创建出既强大又易于维护的Web API。