REST API
Django REST Framework (DRF) 是一个强大的工具包,用于构建 Web API。它基于 Django,提供了序列化、认证、权限等功能。
安装和配置
安装
pip install djangorestframework
pip install markdown # API 文档支持
pip install django-filter # 过滤支持
配置
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
]
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': 10,
}
序列化器
序列化器负责将复杂的数据类型转换为 Python 原生数据类型,以便渲染为 JSON 或其他格式。
基本序列化器
# serializers.py
from rest_framework import serializers
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug']
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True
)
class Meta:
model = Post
fields = [
'id', 'title', 'slug', 'author',
'content', 'category', 'category_id',
'status', 'publish', 'views'
]
read_only_fields = ['views', 'publish']
字段类型
class ExampleSerializer(serializers.Serializer):
# 基本字段
title = serializers.CharField(max_length=200)
content = serializers.CharField()
price = serializers.DecimalField(max_digits=10, decimal_places=2)
quantity = serializers.IntegerField()
is_active = serializers.BooleanField()
# 日期时间字段
created = serializers.DateTimeField()
publish_date = serializers.DateField()
# 关系字段
author = serializers.PrimaryKeyRelatedField(read_only=True)
category = serializers.SlugRelatedField(
slug_field='slug',
queryset=Category.objects.all()
)
# 嵌套序列化器
comments = CommentSerializer(many=True, read_only=True)
# 方法字段
author_name = serializers.SerializerMethodField()
def get_author_name(self, obj):
return obj.author.get_full_name()
验证
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
def validate_title(self, value):
"""验证标题"""
if len(value) < 5:
raise serializers.ValidationError('标题至少需要 5 个字符')
return value
def validate(self, data):
"""多字段验证"""
if data.get('status') == 'published' and not data.get('content'):
raise serializers.ValidationError('发布的文章必须有内容')
return data
视图
函数视图
# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework import status
from .models import Post
from .serializers import PostSerializer
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticatedOrReadOnly])
def post_list(request):
"""文章列表"""
if request.method == 'GET':
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = PostSerializer(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)
@api_view(['GET', 'PUT', 'DELETE'])
def post_detail(request, pk):
"""文章详情"""
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = PostSerializer(post)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = PostSerializer(post, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
类视图
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class PostList(APIView):
"""文章列表视图"""
def get(self, request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(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 PostDetail(APIView):
"""文章详情视图"""
def get_object(self, pk):
try:
return Post.objects.get(pk=pk)
except Post.DoesNotExist:
return None
def get(self, request, pk):
post = self.get_object(pk)
if not post:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post)
return Response(serializer.data)
def put(self, request, pk):
post = self.get_object(pk)
if not post:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
post = self.get_object(pk)
if not post:
return Response(status=status.HTTP_404_NOT_FOUND)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
通用视图
from rest_framework import generics
class PostListCreate(generics.ListCreateAPIView):
"""文章列表和创建"""
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class PostRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
"""文章详情、更新、删除"""
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
ViewSet
from rest_framework import viewsets
class PostViewSet(viewsets.ModelViewSet):
"""文章 ViewSet"""
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def get_queryset(self):
queryset = super().get_queryset()
category = self.request.query_params.get('category')
if category:
queryset = queryset.filter(category__slug=category)
return queryset
# urls.py
from rest_framework.routers import DefaultRouter
from .views import PostViewSet
router = DefaultRouter()
router.register('posts', PostViewSet)
urlpatterns = router.urls
认证
Token 认证
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
# 创建 Token
python manage.py drf_create_token <username>
# views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
"""自定义 Token 获取"""
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(
data=request.data,
context={'request': request}
)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'username': user.username,
})
JWT 认证
pip install djangorestframework-simplejwt
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
权限
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 PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
分页
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
# 自定义分页
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
class PostViewSet(viewsets.ModelViewSet):
pagination_class = CustomPagination
过滤和搜索
# settings.py
INSTALLED_APPS = [
# ...
'django_filters',
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
}
# views.py
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
filterset_fields = ['status', 'category']
search_fields = ['title', 'content']
ordering_fields = ['publish', 'views']
ordering = ['-publish']
完整示例
# models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
class Post(models.Model):
STATUS_CHOICES = [('draft', '草稿'), ('published', '已发布')]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
publish = models.DateTimeField(auto_now_add=True)
views = models.PositiveIntegerField(default=0)
def __str__(self):
return self.title
# serializers.py
from rest_framework import serializers
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug']
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True,
allow_null=True
)
class Meta:
model = Post
fields = [
'id', 'title', 'slug', 'author', 'content',
'category', 'category_id', 'status', 'publish', 'views'
]
read_only_fields = ['views', 'publish']
# views.py
from rest_framework import viewsets, permissions, filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Post
from .serializers import PostSerializer
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 PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related('author', 'category')
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['status', 'category']
search_fields = ['title', 'content']
ordering_fields = ['publish', 'views']
ordering = ['-publish']
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet
router = DefaultRouter()
router.register('posts', PostViewSet)
urlpatterns = [
path('api/', include(router.urls)),
]