跳到主要内容

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)),
]