Django从零开始(七):REST Framework API开发
一、Django REST Framework简介
Django REST Framework(简称DRF)是构建RESTful API的最流行的Django第三方库。它提供了一套完整的工具链,让你能够快速构建强大且灵活的Web API。
DRF核心特性
- 序列化器(Serializer):自动将模型实例转换为JSON/XML等格式
- 视图集(ViewSet):一行代码实现CRUD操作
- 认证与权限:Token、JWT、OAuth等多种认证方式
- 分页与过滤:内置分页和过滤支持
- 可浏览API:自带美观的API文档界面
- 限流(Throttling):防止API被滥用
二、安装与配置
# 安装DRF
pip install djangorestframework
pip install markdown # 可浏览API支持Markdown
pip install django-filter # 过滤支持
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken', # Token认证
'django_filters', # 过滤
]
# DRF全局配置
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_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
# 限流
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour',
'user': '1000/hour',
},
# 日期时间格式
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
三、序列化器(Serializer)
3.1 ModelSerializer
# blog/serializers.py
from rest_framework import serializers
from .models import Article, Category, Tag, Comment
class CategorySerializer(serializers.ModelSerializer):
"""分类序列化器"""
article_count = serializers.IntegerField(read_only=True)
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description', 'article_count', 'created_at']
read_only_fields = ['slug', 'created_at']
class TagSerializer(serializers.ModelSerializer):
"""标签序列化器"""
class Meta:
model = Tag
fields = ['id', 'name', 'slug', 'color']
class ArticleListSerializer(serializers.ModelSerializer):
"""文章列表序列化器(精简版)"""
category_name = serializers.CharField(source='category.name', read_only=True)
author_name = serializers.CharField(source='author.username', read_only=True)
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'summary', 'category_name',
'author_name', 'tags', 'views', 'status',
'published_at', 'created_at',
]
class ArticleDetailSerializer(serializers.ModelSerializer):
"""文章详情序列化器(完整版)"""
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True,
required=False,
)
author_name = serializers.CharField(source='author.username', read_only=True)
tags = TagSerializer(many=True, read_only=True)
tag_ids = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
source='tags',
many=True,
write_only=True,
required=False,
)
comments_count = serializers.SerializerMethodField()
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'content', 'summary',
'category', 'category_id', 'author_name',
'tags', 'tag_ids', 'comments_count',
'views', 'status', 'is_top',
'published_at', 'created_at', 'updated_at',
]
read_only_fields = ['slug', 'views', 'author', 'created_at', 'updated_at']
def get_comments_count(self, obj):
return obj.comments.filter(is_approved=True).count()
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError('标题至少需要5个字符')
return value
class CommentSerializer(serializers.ModelSerializer):
"""评论序列化器"""
author_name = serializers.CharField(source='nickname', read_only=True)
replies = serializers.SerializerMethodField()
class Meta:
model = Comment
fields = [
'id', 'article', 'parent', 'nickname', 'email',
'content', 'is_approved', 'replies', 'created_at',
]
read_only_fields = ['is_approved', 'created_at']
def get_replies(self, obj):
if obj.replies.exists():
return CommentSerializer(obj.replies.all(), many=True).data
return []
3.2 自定义字段和验证
class ArticleCreateSerializer(serializers.ModelSerializer):
"""创建文章的序列化器"""
class Meta:
model = Article
fields = ['title', 'content', 'summary', 'category', 'tags', 'status']
def validate(self, attrs):
# 已发布文章必须有分类
if attrs.get('status') == 'published' and not attrs.get('category'):
raise serializers.ValidationError({
'category': '发布文章必须选择分类'
})
return attrs
def create(self, validated_data):
# 自动设置作者为当前用户
validated_data['author'] = self.context['request'].user
validated_data['slug'] = slugify(validated_data['title'])
tags = validated_data.pop('tags', [])
article = Article.objects.create(**validated_data)
article.tags.set(tags)
return article
def update(self, instance, validated_data):
tags = validated_data.pop('tags', None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if tags is not None:
instance.tags.set(tags)
return instance
四、视图集(ViewSet)
4.1 ModelViewSet
# blog/api_views.py
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from .models import Article, Category, Tag, Comment
from .serializers import (
ArticleListSerializer, ArticleDetailSerializer,
ArticleCreateSerializer, CategorySerializer,
TagSerializer, CommentSerializer,
)
from .permissions import IsAuthorOrReadOnly
from .filters import ArticleFilter
class ArticleViewSet(viewsets.ModelViewSet):
"""文章API视图集"""
queryset = Article.objects.select_related('category', 'author').prefetch_related('tags')
permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ArticleFilter
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'views', 'published_at']
ordering = ['-created_at']
def get_serializer_class(self):
"""根据动作选择序列化器"""
if self.action == 'list':
return ArticleListSerializer
elif self.action == 'retrieve':
return ArticleDetailSerializer
elif self.action in ['create', 'update', 'partial_update']:
return ArticleCreateSerializer
return ArticleDetailSerializer
def perform_create(self, serializer):
"""创建时自动设置作者"""
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""发布文章"""
article = self.get_object()
article.status = 'published'
article.published_at = timezone.now()
article.save()
return Response({'status': 'published'})
@action(detail=True, methods=['post'])
def like(self, request, pk=None):
"""点赞"""
article = self.get_object()
Article.objects.filter(pk=article.pk).update(likes=F('likes') + 1)
return Response({'status': 'liked'})
@action(detail=False, methods=['get'])
def popular(self, request):
"""热门文章"""
articles = self.get_queryset().filter(
status='published'
).order_by('-views')[:10]
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'], url_path='archive')
def archive(self, request):
"""归档统计"""
from django.db.models import Count
archives = Article.objects.filter(
status='published'
).dates('created_at', 'month', order='DESC')
data = []
for date in archives:
count = Article.objects.filter(
created_at__year=date.year,
created_at__month=date.month,
status='published',
).count()
data.append({'date': date.strftime('%Y年%m月'), 'count': count})
return Response(data)
class CategoryViewSet(viewsets.ModelViewSet):
"""分类API"""
queryset = Category.objects.annotate(article_count=Count('articles'))
serializer_class = CategorySerializer
permission_classes = [IsAuthenticatedOrReadOnly]
search_fields = ['name']
class TagViewSet(viewsets.ModelViewSet):
"""标签API"""
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
search_fields = ['name']
class CommentViewSet(viewsets.ModelViewSet):
"""评论API"""
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
return super().get_queryset().filter(is_approved=True)
def perform_create(self, serializer):
serializer.save()
4.2 自定义权限
# blog/permissions.py
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
"""只有作者可以修改,其他人只能查看"""
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限只允许作者
return obj.author == request.user
class IsAdminOrReadOnly(permissions.BasePermission):
"""管理员可写,其他人只读"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return request.user and request.user.is_staff
4.3 过滤器
# blog/filters.py
import django_filters
from .models import Article
class ArticleFilter(django_filters.FilterSet):
"""文章过滤器"""
title = django_filters.CharFilter(lookup_expr='icontains')
status = django_filters.ChoiceFilter(choices=Article.STATUS_CHOICES)
category = django_filters.NumberFilter(field_name='category__id')
category_slug = django_filters.CharFilter(field_name='category__slug')
tag = django_filters.NumberFilter(field_name='tags__id')
date_from = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
date_to = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
min_views = django_filters.NumberFilter(field_name='views', lookup_expr='gte')
class Meta:
model = Article
fields = ['status', 'category', 'tag']
五、路由注册
# blog/api_urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .api_views import ArticleViewSet, CategoryViewSet, TagViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
router.register(r'categories', CategoryViewSet, basename='category')
router.register(r'tags', TagViewSet, basename='tag')
router.register(r'comments', CommentViewSet, basename='comment')
urlpatterns = [
path('', include(router.urls)),
]
# 项目urls.py
urlpatterns += [
path('api/', include('blog.api_urls')),
]
注册后自动生成的API端点:
| 方法 | URL | 功能 |
|---|---|---|
| GET | /api/articles/ | 文章列表 |
| POST | /api/articles/ | 创建文章 |
| GET | /api/articles/{id}/ | 文章详情 |
| PUT | /api/articles/{id}/ | 完整更新 |
| PATCH | /api/articles/{id}/ | 部分更新 |
| DELETE | /api/articles/{id}/ | 删除文章 |
| POST | /api/articles/{id}/publish/ | 发布文章 |
| GET | /api/articles/popular/ | 热门文章 |
六、JWT认证
# 安装
pip install djangorestframework-simplejwt
# settings.py
from datetime import timedelta
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(hours=2),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'AUTH_HEADER_TYPES': ('Bearer',),
}
# urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
urlpatterns += [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
使用JWT访问API:
# 获取Token
curl -X POST http://localhost:8000/api/token/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password123"}'
# 返回
{
"access": "eyJhbGciOiJIUzI1NiIs...",
"refresh": "eyJhbGciOiJIUzI1NiIs..."
}
# 使用Token访问API
curl http://localhost:8000/api/articles/ \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
# 刷新Token
curl -X POST http://localhost:8000/api/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh": "eyJhbGciOiJIUzI1NiIs..."}'
七、API文档自动生成
# 安装 drf-spectacular(推荐,支持OpenAPI 3.0)
pip install drf-spectacular
# settings.py
INSTALLED_APPS += ['drf_spectacular']
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'Blog API',
'DESCRIPTION': '博客系统API文档',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView
urlpatterns += [
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]
访问 http://localhost:8000/api/docs/ 即可看到交互式API文档界面。
八、总结
本章我们学习了使用DRF构建RESTful API:
- DRF安装配置和全局设置
- 序列化器(Serializer)的设计和使用
- 视图集(ViewSet)快速实现CRUD
- 自定义权限、过滤器
- JWT认证实现
- API文档自动生成
下一章我们将学习 Django中间件、信号与缓存,掌握高级请求处理和性能优化技巧。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







