django rest framework:从零开始搭建RESTful API
UV教程:Python多版本管理神器
Django教程:Django项目开发全链路:数据库操作、多环境配置、windows/linux项目部署一站式指南
Django数据库配置:Django多数据库配置:mysql、mongo、redis、达梦
DRF官方:https://www.django-rest-framework.org/
GitHub:https://github.com/encode/django-rest-framework
一、安装DRF
1.运行uv add djangorestframework安装djangorestframework,未使用uv管理工具可使用pip install djangorestframework
2.进入项目目录,运行 uv run python manage.py startapp drfDemo新建应用,未使用uv管理工具可使用python manage.py startapp drfDemo
3.修改settings.py下的INSTALLED_APPS
python
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'drfDemo'
]
4.修改drfDemo>view.py
python
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.views.decorators.http import require_http_methods
@method_decorator(csrf_exempt, name='dispatch')
@method_decorator(require_http_methods(['GET']), name='dispatch')
class DrfDemo(APIView):
def get(self, request):
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功'
}, status=status.HTTP_201_CREATED)
5.新建drfDemo>urls.py
python
from .views import DrfDemo
from django.urls import path
urlpatterns = [
path('api', DrfDemo.as_view(), name='DrfDemo'),
]
python
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('drfDemo/', include('drfDemo.urls')),
path('', admin.site.urls)
]
7.进入项目目录,运行uv run python manage.py runserver 6001启动服务,未使用uv管理工具可使用python manage.py runserver 6001
8.访问http://127.0.0.1:6001/drfDemo/api,如下图所示,说明DRF安装成功
二、DRF通用视图
1.DRF通用视图相关类,官网文档:https://www.django-rest-framework.org/api-guide/generic-views/
类别 | 类名称 | 说明 |
---|---|---|
功能类 | ListModelMixin | 提供列表查询功能(get 方法实现) |
功能类 | CreateModelMixin | 提供创建资源功能(post 方法实现) |
功能类 | RetrieveModelMixin | 提供单资源查询功能(get 方法实现,需指定主键) |
功能类 | UpdateModelMixin | 提供更新资源功能(put/patch 方法实现) |
功能类 | DestroyModelMixin | 提供删除资源功能(delete 方法实现) |
基础通用视图 | GenericAPIView | 所有通用视图的基类,提供核心功能(如查询集、序列化器管理) |
具体视图类 | CreateAPIView | 仅支持创建资源(GenericAPIView + CreateModelMixin) |
具体视图类 | ListAPIView | 仅支持列表查询(GenericAPIView + ListModelMixin) |
具体视图类 | RetrieveAPIView | 仅支持单资源查询(GenericAPIView + RetrieveModelMixin) |
具体视图类 | DestroyAPIView | 仅支持删除资源(GenericAPIView + DestroyModelMixin) |
具体视图类 | UpdateAPIView | 仅支持更新资源(GenericAPIView + UpdateModelMixin) |
具体视图类 | ListCreateAPIView | 支持列表查询和创建资源(ListModelMixin + CreateModelMixin) |
具体视图类 | RetrieveUpdateAPIView | 支持单资源查询和更新(RetrieveModelMixin + UpdateModelMixin) |
具体视图类 | RetrieveDestroyAPIView | 支持单资源查询和删除(RetrieveModelMixin + DestroyModelMixin) |
具体视图类 | RetrieveUpdateDestroyAPIView | 支持单资源查询、更新和删除(Retrieve+Update+DestroyModelMixin) |
2.修改drfDemo>models.py
python
from django.db import models
from django.utils import timezone
class Student(models.Model):
# 性别选择
GENDER_CHOICES = (
('M', '男'),
('F', '女'),
('O', '其他'),
)
# 基本信息
name = models.CharField(max_length=100, verbose_name='姓名')
student_id = models.CharField(max_length=20, unique=True, verbose_name='学号')
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, verbose_name='性别')
birth_date = models.DateField(verbose_name='出生日期')
email = models.EmailField(blank=True, null=True, verbose_name='邮箱')
phone = models.CharField(max_length=15, blank=True, null=True, verbose_name='电话')
# 学业信息
major = models.CharField(max_length=100, verbose_name='专业')
grade = models.PositiveIntegerField(verbose_name='年级')
admission_date = models.DateField(default=timezone.now, verbose_name='入学日期')
# 状态信息
is_active = models.BooleanField(default=True, verbose_name='是否在读')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'student'
verbose_name = '学生'
verbose_name_plural = '学生'
ordering = ['student_id'] # 按学号排序
def __str__(self):
return f'{self.name}({self.student_id})'
def get_age(self):
"""计算学生年龄"""
today = timezone.now().date()
age = today.year - self.birth_date.year
# 考虑生日是否已过
if (today.month, today.day) < (self.birth_date.month, self.birth_date.day):
age -= 1
return age
3.依次运行下面命令同步模型,需配置数据库
python
uv run python manage.py makemigrations drfDemo #根据模型变更创建新的数据库迁移文件,未使用uv管理工具可使用pthon manage.py makemigrations drfDemo
uv run python manage.py migrate #同步模型与数据库,未使用uv管理工具可使用python manage.py migrate
4.新建drfDemo>serializers.py,序列化文件将查询集和模型实例等复杂数据转换为Python数据类型,官网文档:https://www.django-rest-framework.org/api-guide/serializers/
python
from .models import Student
from rest_framework import serializers
class StudentSerializer(serializers.ModelSerializer):
"""
学生信息序列化器,用于学生信息的序列化和反序列化
支持所有字段的增删改查操作
"""
# 只读字段,计算年龄
age = serializers.IntegerField(source='get_age', read_only=True)
created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
updated_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
class Meta:
model = Student
# 包含模型所有字段
fields = '__all__'
# 可以根据需要指定只读字段,如自动生成的时间戳
read_only_fields = ['created_at', 'updated_at']
def validate_phone(self, value):
"""
验证电话号码格式
"""
if value and not (value.isdigit() and len(value) in [11]):
raise serializers.ValidationError('请输入有效的11位手机号码')
return value
def validate_grade(self, value):
"""
验证年级范围(1-6年级)
"""
if value < 1 or value > 6:
raise serializers.ValidationError('年级必须在1-6之间')
return value
5.修改drfDemo>views.py
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics, status
from rest_framework.response import Response
class DrfDemoAddView(generics.GenericAPIView):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def post(self, request):
request_data = request.data # 将请求数据存储在一个变量中
serializer = self.get_serializer(data=request_data)
if serializer.is_valid():
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生添加成功'
}, status=status.HTTP_200_OK)
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '请求失败',
'errors': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
class DrfDemoGetView(generics.GenericAPIView):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request):
students = self.get_queryset()
serializer = self.get_serializer(students, many=True)
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功',
'data': serializer.data
}, status=status.HTTP_200_OK)
class DrfDemoUpdateView(generics.GenericAPIView):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def post(self, request):
"""
通过POST方法修改学生信息
请求数据中需包含id字段指定要修改的学生
"""
# 从请求数据中获取学生ID
student_id = request.data.get('student_id')
if not student_id:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '缺少student_id字段'
}, status=status.HTTP_400_BAD_REQUEST)
# 获取要修改的学生对象
try:
# 这里假设id是学生表的主键,如果是其他字段(如student_id)可修改查询条件
student = self.queryset.get(student_id=student_id)
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': f'ID为{student_id}的学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
# 初始化序列化器(传入要更新的对象和新数据)
serializer = self.get_serializer(
instance=student,
data=request.data,
partial=True # 允许部分字段更新
)
if serializer.is_valid():
serializer.save() # 执行更新操作
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息修改成功'
}, status=status.HTTP_200_OK)
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '请求失败'
}, status=status.HTTP_400_BAD_REQUEST)
class DrfDemoDeleteView(generics.GenericAPIView):
"""提供学生信息删除的API接口(使用GET方法)"""
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request):
"""
通过GET方法删除学生信息
"""
student_id = request.GET.get('student_id')
if not student_id:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '缺少student_id字段'
}, status=status.HTTP_400_BAD_REQUEST)
try:
student = self.queryset.get(student_id=str(student_id))
student_name = student.name # 记录姓名用于返回提示
student.delete() # 执行删除操作(物理删除,若需逻辑删除可改为student.is_active=False + save())
# 4. 返回删除成功响应
return Response({
'code': status.HTTP_200_OK,
'message': f'学生「{student_name}」(student_id:{student_id})删除成功'
}, status=status.HTTP_200_OK)
# 处理学生不存在的情况
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': f'student_id为{student_id}的学生不存在,无法删除'
}, status=status.HTTP_404_NOT_FOUND)
6.修改drfDemo>urls.py
python
from .views import DrfDemoAddView, DrfDemoDeleteView, DrfDemoGetView, DrfDemoUpdateView
from django.urls import path
urlpatterns = [
path('drfDemo/add', DrfDemoAddView.as_view(), name='添加数据'),
path('drfDemo/query', DrfDemoGetView.as_view(), name='查询数据'),
path('drfDemo/update', DrfDemoUpdateView.as_view(), name='修改数据'),
path('drfDemo/delete', DrfDemoDeleteView.as_view(), name='删除数据'),
]
python
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('api/', include('drfDemo.urls')),
path('', admin.site.urls)
]
8.测试增删改查请求,如下图
9.修改drfDemo>views.py,使用功能类优化请求,如:ListModelMixin、
python
CreateModelMixin、UpdateModelMixin、DestroyModelMixin
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics, status
from rest_framework.exceptions import ValidationError
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, UpdateModelMixin
from rest_framework.response import Response
class DrfDemoAddView(generics.GenericAPIView,CreateModelMixin):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def post(self, request, *args, **kwargs):
try:
# 调用create()方法的异常
self.create(request, *args, **kwargs)
return Response({
'code': status.HTTP_200_OK,
'message': '学生添加成功'
}, status=status.HTTP_200_OK)
except ValidationError as e:
# 捕获序列化器验证错误(如字段格式错误)
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '请求失败',
'errors': e.detail # 详细错误信息
}, status=status.HTTP_400_BAD_REQUEST)
class DrfDemoGetView(generics.GenericAPIView, ListModelMixin):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request, *args, **kwargs):
response = self.list(request, *args, **kwargs)
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功',
'data': response.data
}, status=status.HTTP_200_OK)
class DrfDemoUpdateView(generics.GenericAPIView, UpdateModelMixin):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get_object(self):
"""
重写此方法,从请求数据中获取学生ID
"""
student_id = self.request.data.get('student_id')
if not student_id:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '缺少student_id字段'
}, status=status.HTTP_400_BAD_REQUEST)
obj = self.queryset.get(student_id=student_id)
# 检查对象权限(如果需要)
self.check_object_permissions(self.request, obj)
return obj
def post(self, request, *args, **kwargs):
"""
通过POST方法修改学生信息
请求数据中需包含id字段指定要修改的学生
"""
# 从请求数据中获取学生ID
kwargs['partial'] = True
try:
# 调用create()方法的异常
self.update(request, *args, **kwargs)
return Response({
'code': status.HTTP_200_OK,
'message': '修改成功'
}, status=status.HTTP_200_OK)
except ValidationError as e:
# 捕获序列化器验证错误(如字段格式错误)
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '请求失败',
'errors': e.detail # 详细错误信息
}, status=status.HTTP_400_BAD_REQUEST)
class DrfDemoDeleteView(generics.GenericAPIView, DestroyModelMixin):
"""提供学生信息删除的API接口(使用GET方法)"""
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get_object(self):
"""
重写此方法,从请求数据中获取学生ID
"""
student_id = self.request.GET.get('student_id')
if not student_id:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '缺少student_id字段'
}, status=status.HTTP_400_BAD_REQUEST)
obj = self.queryset.get(student_id=student_id)
# 检查对象权限(如果需要)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
"""
通过GET方法删除学生信息
"""
student_id = request.GET.get('student_id')
try:
self.destroy(request, *args, **kwargs)
# 4. 返回删除成功响应
return Response({
'code': status.HTTP_200_OK,
'message': f'学生(student_id:{student_id})删除成功'
}, status=status.HTTP_200_OK)
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': f'student_id为{student_id}的学生不存在,无法删除'
}, status=status.HTTP_404_NOT_FOUND)
10.修改drfDemo>views.py,使用视图类优化请求,如:CreateAPIView、ListAPIView、DestroyAPIView、UpdateAPIView
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics, status
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
class DrfDemoAddView(generics.CreateAPIView):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def post(self, request, *args, **kwargs):
try:
# 调用create()方法的异常
self.create(request, *args, **kwargs)
return Response({
'code': status.HTTP_200_OK,
'message': '学生添加成功'
}, status=status.HTTP_200_OK)
except ValidationError as e:
# 捕获序列化器验证错误(如字段格式错误)
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '请求失败',
'errors': e.detail # 详细错误信息
}, status=status.HTTP_400_BAD_REQUEST)
class DrfDemoGetView(generics.ListAPIView):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request, *args, **kwargs):
response = self.list(request, *args, **kwargs)
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功',
'data': response.data
}, status=status.HTTP_200_OK)
class DrfDemoUpdateView(generics.UpdateAPIView):
""" 提供学生列表查询和创建新学生的API接口 """
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get_object(self):
"""
重写此方法,从请求数据中获取学生ID
"""
student_id = self.request.data.get('student_id')
if not student_id:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '缺少student_id字段'
}, status=status.HTTP_400_BAD_REQUEST)
obj = self.queryset.get(student_id=student_id)
# 检查对象权限(如果需要)
self.check_object_permissions(self.request, obj)
return obj
def post(self, request, *args, **kwargs):
"""
通过POST方法修改学生信息
请求数据中需包含id字段指定要修改的学生
"""
# 从请求数据中获取学生ID
kwargs['partial'] = True
try:
# 调用create()方法的异常
self.update(request, *args, **kwargs)
return Response({
'code': status.HTTP_200_OK,
'message': '修改成功'
}, status=status.HTTP_200_OK)
except ValidationError as e:
# 捕获序列化器验证错误(如字段格式错误)
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '请求失败',
'errors': e.detail # 详细错误信息
}, status=status.HTTP_400_BAD_REQUEST)
class DrfDemoDeleteView(generics.DestroyAPIView):
"""提供学生信息删除的API接口(使用GET方法)"""
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get_object(self):
"""
重写此方法,从请求数据中获取学生ID
"""
student_id = self.request.GET.get('student_id')
if not student_id:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': '缺少student_id字段'
}, status=status.HTTP_400_BAD_REQUEST)
obj = self.queryset.get(student_id=student_id)
# 检查对象权限(如果需要)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
"""
通过GET方法删除学生信息
"""
student_id = request.GET.get('student_id')
try:
self.destroy(request, *args, **kwargs)
# 4. 返回删除成功响应
return Response({
'code': status.HTTP_200_OK,
'message': f'学生(student_id:{student_id})删除成功'
}, status=status.HTTP_200_OK)
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': f'student_id为{student_id}的学生不存在,无法删除'
}, status=status.HTTP_404_NOT_FOUND)
三、DRF视图集
1.DRF视图集相关,官网文档:https://www.django-rest-framework.org/api-guide/viewsets/
视图集类名 | 说明 |
---|---|
ViewSet | 基础ViewSet类,提供action映射机制但无具体实现,需手动实现list、retrieve等方法 |
GenericViewSet | 继承GenericAPIView,提供通用视图功能但无默认action实现,需手动实现具体方法 |
ModelViewSet | 完整实现类,继承GenericViewSet并包含所有标准CRUD操作,自动提供全部action |
ReadOnlyModelViewSet | 只读版本,仅提供list和retrieve两个只读操作,不包含创建更新删除功能 |
视图集内置方法 | HTTP 方法 | URL 路径 | 描述 |
---|---|---|---|
list | GET | /path/ | 获取资源列表 |
retrieve | GET | /path/{id}/ | 获取单个资源详情 |
create | POST | /path/ | 创建新资源 |
update | PUT | /path/{id}/ | 全量更新资源 |
partial_update | PATCH | /path/{id}/ | 部分更新资源 |
destroy | DELETE | /path/{id}/ | 删除资源 |
2.修改drfDemo>views.py,使用ViewSet优化Student请求 |
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.response import Response
class DrfDemoView(viewsets.ViewSet):
@staticmethod
def get_queryset():
"""获取所有学生数据"""
return Student.objects.all()
@staticmethod
def get_serializer(*args, **kwargs):
"""获取序列化器"""
return StudentSerializer(*args, **kwargs)
def list(self, request, *args, **kwargs):
"""
获取所有学生
GET /api/students/
"""
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功',
'data': serializer.data
}, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
"""
创建新学生
POST /api/students/
"""
try:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生添加成功'
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': f'创建学生失败: {str(e)}'
}, status=status.HTTP_400_BAD_REQUEST)
def update(self, request, pk=None):
"""
更新学生信息
PUT /api/students/{id}/
"""
try:
student = self.get_queryset().get(student_id=pk)
serializer = self.get_serializer(student, data=request.data,partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息更新成功',
'data': serializer.data
})
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': f'更新学生失败: {str(e)}',
'errors': serializer.errors if 'serializer' in locals() else None
}, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk=None):
"""
删除学生信息
DELETE /api/students/{id}/
"""
try:
student = self.get_queryset().get(student_id=pk)
student.delete()
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息删除成功'
})
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({
'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
'message': f'删除学生失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
3.修改drfDemo>urls.py,官网文档:https://www.django-rest-framework.org/api-guide/routers/
python
'''
GET http://127.0.0.1:6001/api/drfDemo/ 获取
POST http://127.0.0.1:6001/api/drfDemo/ 添加
PUT http://127.0.0.1:6001/api/drfDemo/1/ 更新,1为学生ID
DELETE http://127.0.0.1:6001/api/drfDemo/1/ 删除,1为学生ID
'''
from .views import DrfDemoView
from rest_framework import routers
# 创建路由器
router = routers.SimpleRouter()
router.register(r'drfDemo', DrfDemoView, basename='drfDemo')
urlpatterns = router.urls
4.修改drfDemo>views.py,使用GenericViewSet优化Student请求,内置get_queryset、get_serializer等方法,但是需要配置queryset、serializer_class
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.response import Response
class DrfDemoView(viewsets.GenericViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def list(self, request, *args, **kwargs):
"""
获取所有学生
GET /api/students/
"""
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功',
'data': serializer.data
}, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
"""
创建新学生
POST /api/students/
"""
try:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生添加成功'
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': f'创建学生失败: {str(e)}'
}, status=status.HTTP_400_BAD_REQUEST)
def update(self, request, pk=None):
"""
更新学生信息
PUT /api/students/{id}/
"""
try:
student = self.get_queryset().get(student_id=pk)
serializer = self.get_serializer(student, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息更新成功',
'data': serializer.data
})
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': f'更新学生失败: {str(e)}',
'errors': serializer.errors if 'serializer' in locals() else None
}, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk=None):
"""
删除学生信息
DELETE /api/students/{id}/
"""
try:
student = self.get_queryset().get(student_id=pk)
student.delete()
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息删除成功'
})
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({
'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
'message': f'删除学生失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
5.修改drfDemo>views.py,使用ModelViewSet优化Student请求,ModelViewSet内置list、create、update、delete等请求,如需自定义响应结果结构可参考GenericViewSet
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import viewsets
class DrfDemoView(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
6.由于视图集中内置请求都是的path都是固定的,如需达到图二的效果,需自定义action
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
class DrfDemoView(viewsets.GenericViewSet):
serializer_class = StudentSerializer
@action(detail=False, methods=['get'])
def query(self, request, *args, **kwargs):
"""
获取所有学生
"""
queryset = Student.objects.all()
serializer = self.get_serializer(queryset, many=True)
return Response({
'code': status.HTTP_200_OK,
'message': '请求成功',
'data': serializer.data
}, status=status.HTTP_200_OK)
@action(detail=False, methods=['post'])
def add(self, request, *args, **kwargs):
"""
获取所有学生
"""
try:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生添加成功'
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': f'创建学生失败: {str(e)}'
}, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, url_path='update',methods=['post'])
def new_update(self, request, *args, **kwargs):
"""
获取所有学生
"""
try:
queryset = Student.objects.all()
student = queryset.get(student_id=request.data['student_id'])
serializer = self.get_serializer(student, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息更新成功'
})
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({
'code': status.HTTP_400_BAD_REQUEST,
'message': f'更新学生失败: {str(e)}'
}, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['post'])
def delete(self, request, *args, **kwargs):
try:
queryset = Student.objects.all()
student = queryset.get(student_id=request.data['student_id'])
student.delete()
return Response({
'code': status.HTTP_200_OK,
'message': '学生信息删除成功'
})
except Student.DoesNotExist:
return Response({
'code': status.HTTP_404_NOT_FOUND,
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({
'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
'message': f'删除学生失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
四、统一请求响应结果格式
1.setting.py同目录新建renderers.py渲染器,统一请求响应结果格式,官网文档:https://www.django-rest-framework.org/api-guide/renderers/
python
from rest_framework import status
from rest_framework.renderers import JSONRenderer as DjangoJSONRenderer
class JSONRenderer(DjangoJSONRenderer):
"""
自定义JSON渲染器,统一所有状态码的响应格式
响应格式:
- 成功 (2xx): {"status": "success", "code": 200, "message": "操作成功", "data": {...}}
- 重定向 (3xx): {"status": "redirect", "code": 301, "message": "重定向", "data": {...}}
- 客户端错误 (4xx): {"status": "error", "code": 400, "message": "错误信息", "errors": [...]}
- 服务器错误 (5xx): {"status": "error", "code": 500, "message": "服务器错误", "errors": []}
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
if renderer_context is None:
renderer_context = {}
response = renderer_context.get('response')
status_code = getattr(response, 'status_code', status.HTTP_200_OK)
data = data or {}
# 定义状态码到状态文本的映射
status_mapping = {
'success': range(200, 300), # 2xx
'redirect': range(300, 400), # 3xx
'error': range(400, 600), # 4xx和5xx
}
# 确定状态文本
response_status = 'success'
for status_text, code_range in status_mapping.items():
if status_code in code_range:
response_status = status_text
break
# 默认消息映射
default_messages = {
# 2xx 成功
status.HTTP_200_OK: '请求成功',
status.HTTP_201_CREATED: '创建成功',
status.HTTP_202_ACCEPTED: '请求已接受',
status.HTTP_204_NO_CONTENT: '删除成功',
# 3xx 重定向
status.HTTP_301_MOVED_PERMANENTLY: '永久重定向',
status.HTTP_302_FOUND: '临时重定向',
status.HTTP_304_NOT_MODIFIED: '资源未修改',
# 4xx 客户端错误
status.HTTP_400_BAD_REQUEST: '请求错误',
status.HTTP_401_UNAUTHORIZED: '未授权',
status.HTTP_403_FORBIDDEN: '禁止访问',
status.HTTP_404_NOT_FOUND: '资源不存在',
status.HTTP_405_METHOD_NOT_ALLOWED: '方法不允许',
status.HTTP_409_CONFLICT: '资源冲突',
status.HTTP_422_UNPROCESSABLE_ENTITY: '参数验证失败',
# 5xx 服务器错误
status.HTTP_500_INTERNAL_SERVER_ERROR: '服务器内部错误',
status.HTTP_502_BAD_GATEWAY: '网关错误',
status.HTTP_503_SERVICE_UNAVAILABLE: '服务不可用',
}
# 获取消息
message = default_messages.get(status_code, '操作完成')
# 构建统一的响应格式
if 200 <= status_code < 300:
# 成功响应
if isinstance(data, dict) and 'status' in data and 'data' in data:
# 已经是自定义格式,保持原样
final_data = data
else:
final_data = {
'status': response_status,
'code': status_code,
'message': message,
'data': data
}
elif 300 <= status_code < 400:
# 重定向响应
final_data = {
'status': response_status,
'code': status_code,
'message': message,
'data': {
'redirect_url': data.get('url', data.get('redirect_url', '')) if isinstance(data, dict) else '',
'original_data': data
}
}
else:
# 错误响应 (4xx和5xx)
final_data = {
'status': response_status,
'code': status_code,
'message': message,
'data': None
}
if 'errors' in data:
errors = data['errors']
final_data['errors'] = errors
return super().render(final_data, accepted_media_type, renderer_context)
2.修改setting.py,增加REST_FRAMEWORK配置
python
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'demo.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer', # 保留可浏览API
]
}
3.修改drfDemo>views.py
python
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
class DrfDemoView(viewsets.GenericViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
@action(detail=False, methods=['get'])
def query(self, request, *args, **kwargs):
"""
获取所有学生
"""
queryset = Student.objects.all()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@action(detail=False, methods=['post'])
def add(self, request, *args, **kwargs):
"""
获取所有学生
"""
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(request.data, status=status.HTTP_200_OK)
except ValidationError:
return Response({
'errors': serializer.errors
},status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, url_path='update',methods=['post'])
def new_update(self, request, *args, **kwargs):
"""
获取所有学生
"""
try:
queryset = self.get_queryset()
student = queryset.get(student_id=request.data['student_id'])
serializer = self.get_serializer(student, data=request.data, partial=True)
try:
serializer.is_valid(raise_exception=True)
except ValidationError:
return Response({
'errors': serializer.errors
},status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
except KeyError:
return Response({
'errors': '缺少student_id参数'
}, status=status.HTTP_400_BAD_REQUEST)
except Student.DoesNotExist:
return Response({
'errors': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
@action(detail=False, methods=['post'])
def delete(self, request, *args, **kwargs):
try:
queryset = self.get_queryset()
student = queryset.get(student_id=request.data['student_id'])
student.delete()
return Response(status=status.HTTP_200_OK)
except KeyError:
return Response({
'errors': '缺少student_id参数'
}, status=status.HTTP_400_BAD_REQUEST)
except Student.DoesNotExist:
return Response({
'message': '学生不存在'
}, status=status.HTTP_404_NOT_FOUND)
4.效果图如下
五、JWT认证配置
1.运行uv add djangorestframework-simplejwt --dev安装djangorestframework-simplejwt,未使用uv管理工具可使用pip install djangorestframework-simplejwt,官网文档:https://django-rest-framework-simplejwt.readthedocs.io/en/latest/
2.进入项目目录,运行 uv run python manage.py startapp users新建应用,未使用uv管理工具可使用python manage.py startapp users
python
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'drfDemo',
'users',
]
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'demo.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
SIMPLE_JWT = {
# Token有效时间
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
# Refresh Token有效时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
# 是否允许刷新Token
'ROTATE_REFRESH_TOKENS': False,
# 是否在刷新Token时将旧Token加入黑名单
'BLACKLIST_AFTER_ROTATION': True,
# Token验证设置
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
# Token验证字段
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
# 权限验证
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
# 刷新Token的Sliding Token设置
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
4.修改users>view.py
python
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
class UserView(viewsets.GenericViewSet):
"""
登录视图集,使用GenericViewSet实现
提供登录功能,返回JWT令牌和用户信息
"""
permission_classes = [AllowAny]
@action(detail=False, methods=['post'])
def login2(self, request, *args, **kwargs):
"""
处理登录请求
POST /api/login/
"""
user_info = {
'user_id': '001',
'username': request.data.get('username'),
'password': request.data.get('password')
}
# 验证输入
if not user_info['username'] or not user_info['password']:
return Response(
{'detail': '用户名和密码不能为空'},
status=status.HTTP_400_BAD_REQUEST
)
# 生成JWT令牌
refresh = RefreshToken()
refresh.payload.update(user_info)
return Response({
**user_info,
'token': str(refresh.access_token)
})
5.创建users>urls.py
python
from .views import UserView
from rest_framework import routers
# 创建路由器
router = routers.SimpleRouter()
router.register(r'users', UserView, basename='user')
urlpatterns = router.urls
python
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('api/', include('users.urls')),
path('api/', include('drfDemo.urls')),
path('', admin.site.urls)
]
7.访问/api/drfDemo/add/请求会发现请求报未授权,说明JWT已配置
8.验证JWT配置成功:访问/api/users/login/,拿到token,这里的账户密码是django后台管理的账户
9.验证JWT配置成功:使用/api/users/login/请求拿到的token,再次请求/api/drfDemo/add/,会发现请求成功,说明JWT配置成功
六、生成API文档
1.运行 uv add drf-spectacular安装drf-spectacular,未使用uv管理工具可使用pip install drf-spectacular,官网文档:https://drf-spectacular.readthedocs.io/en/latest/
python
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'drf_spectacular',
'drfDemo',
'users',
]
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'demo.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema'
}
SPECTACULAR_SETTINGS = {
'TITLE': 'API文档', # 标题
'DESCRIPTION': 'API接口文档描述', # 描述
'VERSION': '1.0.0', # 版本
'SERVE_INCLUDE_SCHEMA': False, # 不包含schema本身的端点
'TAGS': [
{'name': '用户管理', 'description': '用户登录等相关接口'}, # 第1个显示
{'name': '学生管理', 'description': '学生信息CRUD接口'}, # 第2个显示
],
'SWAGGER_UI_SETTINGS': {
'defaultModelsExpandDepth': -1, # 不显示任何模型(包括Schemas分组)
},
}
python
from django.contrib import admin
from django.urls import include, path
from drf_spectacular.views import (
SpectacularAPIView, # 生成OpenAPI schema文件
SpectacularRedocView, # ReDoc
SpectacularSwaggerView, # Swagger UI
)
urlpatterns = [
path('api/', include('users.urls')),
path('api/', include('drfDemo.urls')),
path('api/schema', SpectacularAPIView.as_view(), name='schema'), # schema文件
path('swagger', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger'),
path('redoc', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
path('', admin.site.urls)
]
4.修改drfDemo>serializers.py
python
from .models import Student
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
class StudentSerializer(serializers.ModelSerializer):
"""
学生信息序列化器,用于学生信息的序列化和反序列化
支持所有字段的增删改查操作
"""
# 第一种字段校验方式
name = serializers.CharField(
max_length=100,
error_messages={'max_length': '姓名必须是11位数字', 'required': '缺少name参数'},
)
student_id = serializers.CharField(
required=True,
max_length=20,
error_messages={
'required': '缺少student_id参数',
'max_length': '学号最大长度为20字符'
},
# 显式添加唯一校验器
validators=[
UniqueValidator(
queryset=Student.objects.all(),
message='学号已存在,请更换' # 自定义错误提示
)
]
)
gender = serializers.ChoiceField(
choices=Student.GENDER_CHOICES,
error_messages={
'required': '缺少gender参数',
'invalid_choice': f'性别必须是 {[choice[0] for choice in Student.GENDER_CHOICES]} 中的一个',
},
)
birth_date = serializers.DateField(
error_messages={'required': '缺少birth_date参数', 'invalid': '出生日期格式错误,请使用YYYY-MM-DD'}
)
email = serializers.EmailField(
required=False,
allow_blank=True, # 允许空字符串
error_messages={
'invalid': '邮箱格式错误(例如:example@domain.com)' # 自定义错误提示
},
)
major = serializers.CharField(
max_length=100,
error_messages={'max_length': '专业最大长度100', 'required': '缺少major参数'},
)
grade = serializers.IntegerField(
min_value=1,
max_value=6,
error_messages={
'required': '缺少grade参数',
'min_value': '年级必须大于等于1',
'max_value': '年级必须小于等于6',
'invalid': '年级必须是数字',
},
)
admission_date = serializers.DateField(
required=False,
error_messages={'invalid': '入学日期格式错误,请使用YYYY-MM-DD'}
)
is_active = serializers.BooleanField(default=True)
age = serializers.IntegerField(source='get_age', read_only=True)
created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
updated_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
class Meta:
model = Student
# 包含模型所有字段
fields = '__all__'
# 可以根据需要指定只读字段,如自动生成的时间戳
read_only_fields = ['created_at', 'updated_at']
# 第二种字段校验方式
extra_kwargs = {
# 入学日期:可选 + 格式校验
'admission_date': {
'required': False,
'error_messages': {
'invalid': '入学日期格式错误,请使用YYYY-MM-DD'
}
}
}
# 第三种字段校验方式
def validate_phone(self, value):
"""
验证电话号码格式
"""
if value and not (value.isdigit() and len(value) in [11]):
raise serializers.ValidationError('手机号必须是11位数字')
return value
class StudentIdValidate(serializers.Serializer):
"""
学生信息序列化器,用于学生信息的序列化和反序列化
支持所有字段的增删改查操作
"""
student_id = serializers.CharField(
required=True,
max_length=20,
error_messages={
'required': '缺少student_id参数',
'max_length': '学号最大长度为20字符'
}
)
5.修改drfDemo>views.py
python
from .models import Student
from .serializers import StudentIdValidate, StudentSerializer
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
@extend_schema_view(
query=extend_schema(
tags=['学生管理'],
summary='获取所有学生',
description='查询数据库中所有学生的信息列表',
),
add=extend_schema(
tags=['学生管理'],
summary='添加新学生',
description='通过提交学生信息(如姓名、学号等)创建新学生记录',
responses={
200: OpenApiResponse(
description='创建成功,返回新学生信息',
response=StudentSerializer
)
}
),
new_update=extend_schema(
tags=['学生管理'],
summary='更新学生',
description='根据student_id更新指定学生的信息(支持部分字段更新)',
responses={
200: OpenApiResponse(
description='更新成功,返回更新后的学生信息',
response=StudentSerializer
)
}
),
delete=extend_schema(
tags=['学生管理'],
summary='删除学生',
description='根据student_id删除指定的学生记录',
request=StudentIdValidate,
responses={
200: OpenApiResponse(description='删除成功'),
400: OpenApiResponse(description='缺少student_id参数'),
404: OpenApiResponse(description='学生不存在')
}
),
)
class DrfDemoView(viewsets.GenericViewSet):
swagger_tags = ['学生管理']
queryset = Student.objects.all()
serializer_class = StudentSerializer
@action(detail=False, methods=['get'])
def query(self, request, *args, **kwargs):
"""
获取所有学生
"""
queryset = Student.objects.all()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@action(detail=False, methods=['post'])
def add(self, request, *args, **kwargs):
"""
获取所有学生
"""
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(request.data, status=status.HTTP_200_OK)
except ValidationError:
return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, url_path='update',methods=['post'])
def new_update(self, request, *args, **kwargs):
"""
获取所有学生
"""
student_id_validate = StudentIdValidate(data=request.data)
try:
student_id_validate.is_valid(raise_exception=True)
queryset = self.get_queryset()
student = queryset.get(student_id=request.data['student_id'])
serializer = self.get_serializer(student, data=request.data, partial=True)
try:
serializer.is_valid(raise_exception=True)
except ValidationError:
return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
except ValidationError:
return Response({'errors': student_id_validate.errors}, status=status.HTTP_400_BAD_REQUEST)
except Student.DoesNotExist:
return Response({'errors': '学生不存在'}, status=status.HTTP_404_NOT_FOUND)
@action(detail=False, methods=['post'])
def delete(self, request, *args, **kwargs):
student_id_validate = StudentIdValidate(data=request.data)
try:
student_id_validate.is_valid(raise_exception=True)
queryset = self.get_queryset()
student = queryset.get(student_id=request.data['student_id'])
student.delete()
return Response(status=status.HTTP_200_OK)
except ValidationError:
return Response({'errors': student_id_validate.errors}, status=status.HTTP_400_BAD_REQUEST)
except Student.DoesNotExist:
return Response({'message': '学生不存在'}, status=status.HTTP_404_NOT_FOUND)
6.新增users>serializers.py
python
from rest_framework import serializers
class LoginValidate(serializers.Serializer):
username = serializers.CharField(error_messages={'required': '缺少username参数'})
password = serializers.CharField(error_messages={'required': '缺少password参数'})
class LoginSuccessResponse(serializers.Serializer):
user_id = serializers.CharField()
username = serializers.CharField()
password = serializers.CharField()
access_token = serializers.CharField()
7.新增users>views.py
python
from .serializers import LoginSuccessResponse, LoginValidate
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
@extend_schema_view(
login=extend_schema(
tags=['用户管理'],
summary='用户登录',
description='通过用户名和密码登录,返回JWT令牌',
request=LoginValidate,
responses=OpenApiResponse(
description='登录成功,返回用户信息和JWT令牌',
response=LoginSuccessResponse
)
)
)
class UserView(viewsets.GenericViewSet):
"""
登录视图集,使用GenericViewSet实现
提供登录功能,返回JWT令牌和用户信息
"""
permission_classes = [AllowAny]
@action(detail=False, methods=['post'])
def login(self, request, *args, **kwargs):
"""
处理登录请求
POST /api/login/
"""
login_validate = LoginValidate(data=request.data)
try:
login_validate.is_valid(raise_exception=True)
user_info = {
'user_id': '001',
'username': request.data.get('username'),
'password': request.data.get('password')
}
# 生成JWT令牌
refresh = RefreshToken()
refresh.payload.update(user_info)
return Response({
**user_info,
'access_token': str(refresh.access_token)
})
except ValidationError:
return Response({'errors': login_validate.errors}, status=status.HTTP_400_BAD_REQUEST)
8.Swagger UI文档访问http://127.0.0.1:6001/swagger#/,官网文档:https://swagger.io/docs
9.ReDoc文档访问http://127.0.0.1:6001/redoc,官网文档:https://redocly.com/redoc
10.配置详解
python
SPECTACULAR_DEFAULTS = {
# 一个正则表达式,用于指定所有操作路径的共同前缀。如果
# SCHEMA_PATH_PREFIX 设置为 None,drf-spectacular 将尝试估算
# 一个共同前缀。使用 '' 可禁用。
# 主要用于标签提取,例如路径 '/api/v1/albums' 搭配
# SCHEMA_PATH_PREFIX 正则 '/api/v[0-9]' 会生成标签 'albums'。
'SCHEMA_PATH_PREFIX': None,
# 从操作路径中移除匹配的 SCHEMA_PATH_PREFIX。通常与
# SERVERS 中附加的前缀配合使用。
'SCHEMA_PATH_PREFIX_TRIM': False,
# 手动向操作路径插入一个前缀,例如 '/service/backend'。
# 例如,当 API 作为子资源挂载在代理后面而 Django 未察觉时,
# 可使用此配置对齐路径。也可通过 SERVERS 指定前缀,但此配置
# 能让操作路径更明确。
'SCHEMA_PATH_PREFIX_INSERT': '',
# {pk} 到 {id} 的转换由 SCHEMA_COERCE_PATH_PK 控制。此外,
# 某些库(如 drf-nested-routers)使用 "_pk" 后缀的路径变量。
# 此设置全局将类似 "{user_pk}" 的路径变量转换为 "{user_id}"。
'SCHEMA_COERCE_PATH_PK_SUFFIX': False,
# 影响组件构建方式的 schema 生成参数。
# 某些 schema 功能可能无法很好地适配目标场景。
# 拆分/修改组件可能有助于缓解这些问题。
'DEFAULT_GENERATOR_CLASS': 'drf_spectacular.generators.SchemaGenerator',
# 为 PATCH 端点创建单独的组件(不含 required 列表)
'COMPONENT_SPLIT_PATCH': True,
# 在适当情况下将组件拆分为请求和响应部分
# 强烈推荐启用此设置以获得最准确的 API 描述,
# 但代价是会生成更多组件。
'COMPONENT_SPLIT_REQUEST': False,
# 帮助那些难以处理只读属性的客户端生成器目标。
'COMPONENT_NO_READ_ONLY_REQUIRED': False,
# 为不允许空字符串的字段添加 "minLength: 1"。默认禁用,
# 因为序列化器不会在响应中严格执行此规则,因此
# "minLength: 1" 可能并不总是准确描述 API 行为。
# 当启用 COMPONENT_SPLIT_REQUEST 时会隐式启用,因为
# 分离请求和响应组件后可以准确建模。
'ENFORCE_NON_BLANK_FIELDS': False,
# 此版本字符串将显示在 schema 头部。默认 OpenAPI 版本为 3.0.3,
# 经过充分测试。现在也支持 3.1.0,包含相同功能和一些
# 强制性但次要的更改。
'OAS_VERSION': '3.0.3',
# 用于 SpectacularAPIView 提供 schema 子集的配置
'SERVE_URLCONF': None,
# 完整的公共 schema 或基于请求用户的子集
'SERVE_PUBLIC': True,
# 将 schema 端点包含到 schema 中
'SERVE_INCLUDE_SCHEMA': True,
# spectacular 视图的认证/权限类列表
'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'],
# None 将默认使用 DRF 的 AUTHENTICATION_CLASSES
'SERVE_AUTHENTICATION': None,
# 传递给 SwaggerUI({ ... }) 的通用配置字典
# https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
# 配置通过 json.dumps() 序列化。如果需要自定义 JS,可使用字符串,
# 字符串必须包含有效的 JS 且会原样传递。
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
},
# 使用额外的 OAuth2 配置初始化 SwaggerUI
# https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/
'SWAGGER_UI_OAUTH2_CONFIG': {},
# 传递给 Redoc.init({ ... }) 的通用配置字典
# https://redocly.com/docs/redoc/config/#functional-settings
# 配置通过 json.dumps() 序列化。如果需要自定义 JS,可使用字符串,
# 字符串必须包含有效的 JS 且会原样传递。
'REDOC_UI_SETTINGS': {},
# swagger 和 redoc 的 CDN。可根据需求更改版本甚至自行托管。
# 关于自行托管,参见 README 中的 sidecar 选项。
'SWAGGER_UI_DIST': 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@latest',
'SWAGGER_UI_FAVICON_HREF': 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@latest/favicon-32x32.png',
'REDOC_DIST': 'https://cdn.jsdelivr.net/npm/redoc@latest',
# 向路径和组件追加 OpenAPI 对象(附加到生成的对象)
'APPEND_PATHS': {},
'APPEND_COMPONENTS': {},
# 强烈不推荐(除了 djangorestframework-api-key 库)
# 请不要再使用此配置,因为它可能带来难以解决的问题。
# 对于认证,强烈推荐使用 OpenApiAuthenticationExtension,
# 因为它们更健壮且易于编写。
# 但如果使用,方法列表会附加到 schema 中的每个端点!
'SECURITY': [],
# 在 schema 生成结束时运行的后处理函数。
# 必须满足接口:result = hook(generator, request, public, result)
'POSTPROCESSING_HOOKS': [
'drf_spectacular.hooks.postprocess_schema_enums'
],
# 在 schema 生成前运行的预处理函数。
# 必须满足接口:result = hook(endpoints=result),其中 result 是
# 元组列表 (path, path_regex, method, callback)。
# 示例:'drf_spectacular.hooks.preprocess_exclude_path_format'
'PREPROCESSING_HOOKS': [],
# 确定操作应如何排序。如果打算通过 PREPROCESSING_HOOKS 进行排序,
# 请确保禁用此设置。如果配置,排序将在 PREPROCESSING_HOOKS 之后应用。
# 接受 True(drf-spectacular 的字母排序器)、False 或 sort 键的可调用对象。
'SORT_OPERATIONS': True,
# 枚举名称覆盖。键为 "YourEnum" 且值为 "field.choices" 的字典
# 例如:{'SomeEnum': ['A', 'B'], 'OtherEnum': 'import.path.to.choices'}
'ENUM_NAME_OVERRIDES': {},
# 在适当情况下添加 "blank" 和 "null" 枚举选项。在客户端生成有问题时禁用
'ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE': True,
# 向枚举描述字符串添加/追加 (``选项值`` - 选项名称) 列表
'ENUM_GENERATE_CHOICE_DESCRIPTION': True,
# 生成的枚举的可选后缀
# 例如:{'ENUM_SUFFIX': "Type"} 会生成枚举名称 'StatusType'
'ENUM_SUFFIX': 'Enum',
# 返回应从文档字符串提取中排除的所有类的列表的函数
'GET_LIB_DOC_EXCLUDES': 'drf_spectacular.plumbing.get_lib_doc_excludes',
# 返回用于视图处理的模拟请求的函数。对于 CLI 使用,
# original_request 将为 None。
# 接口:request = build_mock_request(method, path, view, original_request, **kwargs)
'GET_MOCK_REQUEST': 'drf_spectacular.plumbing.build_mock_request',
# 将 "operationId" 和路径参数名称等驼峰化
# 操作 schema 本身的驼峰化需要添加
# 'drf_spectacular.contrib.djangorestframework_camel_case.camelize_serializer_fields'
# 到 POSTPROCESSING_HOOKS。请注意,此钩子依赖于
# ``djangorestframework_camel_case``,而 CAMELIZE_NAMES 本身不依赖。
'CAMELIZE_NAMES': False,
# 更改生成的 OperationId 中动作/方法的位置。例如,
# "POST": "group_person_list", "group_person_create"
# "PRE": "list_group_person", "create_group_person"
'OPERATION_ID_METHOD_POSITION': 'POST',
# 确定是否以及如何在 schema 中生成自由格式的 'additionalProperties'。
# 某些代码生成器目标对此敏感。None 禁用通用的 'additionalProperties'。
# 允许的值为 'dict'、'bool'、None
'GENERIC_ADDITIONAL_PROPERTIES': 'dict',
# 路径转换器 schema 覆盖(例如 <int:foo>)。可用于修改默认行为
# 或为通过 register_converter(...) 注册的自定义转换器提供 schema。
# 以转换器标签为键,以基本 Python 类型、OpenApiType 或原始 schema 为值。
# 示例:{'aint': OpenApiTypes.INT, 'bint': str, 'cint': {'type': ...}}
'PATH_CONVERTER_OVERRIDES': {},
# 确定操作参数应按字母数字排序还是按原始顺序排列。
# 接受 True、False 或 sort 键的可调用对象。
'SORT_OPERATION_PARAMETERS': True,
# @extend_schema 允许指定 200 以外的状态码。此功能通常用于
# 描述错误响应,很少使用列表机制。因此,默认情况下我们会在
# 非 2XX 状态码上抑制列表(分页和过滤)。切换此设置可启用
# 无论状态码如何,都使用 ListSerializers/many=True 的列表响应。
'ENABLE_LIST_MECHANICS_ON_NON_2XX': False,
# 此设置允许你通过访问不同的模型属性来偏离默认管理器。
# 为了兼容性,我们默认使用 "objects"。使用 "_default_manager"
# 可能会解决大多数问题,但你也可以自由选择任何名称。
"DEFAULT_QUERY_MANAGER": 'objects',
# 控制在 schema 中暴露哪些认证方法。如果不为 None,将隐藏
# 不在白名单中的认证类。使用完整的导入路径,
# 例如 ['rest_framework.authentication.TokenAuthentication', ...]。
# 空列表 ([]) 将隐藏所有认证方法。默认 None 将显示所有。
'AUTHENTICATION_WHITELIST': None,
# 控制在 schema 中暴露哪些解析器。工作方式类似于 AUTHENTICATION_WHITELIST。
# 允许的解析器列表或 None(允许所有)。
'PARSER_WHITELIST': None,
# 控制在 schema 中暴露哪些渲染器。工作方式类似于 AUTHENTICATION_WHITELIST。
# 如果白名单为 None,默认忽略 rest_framework.renderers.BrowsableAPIRenderer
'RENDERER_WHITELIST': None,
# 关闭错误和警告消息的选项
'DISABLE_ERRORS_AND_WARNINGS': False,
# 作为 "./manage.py check --deploy" 的一部分运行示例性 schema 生成并发出警告
'ENABLE_DJANGO_DEPLOY_CHECK': True,
# 通用 schema 元数据。参考规范获取有效输入
# https://spec.openapis.org/oas/v3.0.3#openapi-object
'TITLE': '',
'DESCRIPTION': '',
'TOS': None,
# 可选:可以包含 "name"、"url"、"email"
'CONTACT': {},
# 可选:必须包含 "name",可以包含 URL
'LICENSE': {},
# 静态设置 schema 版本。也可以是空字符串。当与视图版本控制一起使用时,
# 对于 'v2' 版本的请求,将变为 '0.0.0 (v2)'。
# 如果只想渲染请求版本,将 VERSION 设置为 None。
'VERSION': '0.0.0',
# 可选的服务器列表。
# 每个条目必须包含 "url",可以包含 "description"、"variables"
# 例如:[{'url': 'https://example.com/v1', 'description': '文本'}, ...]
'SERVERS': [],
# 在全局范围内定义的标签
'TAGS': [],
# 可选:OpenAPI 3.1 webhooks 列表。每个条目应为 OpenApiWebhook 实例的导入路径。
'WEBHOOKS': [],
# 可选:必须包含 'url',可以包含 "description"
'EXTERNAL_DOCS': {},
# 附加到 schema 的 info 对象的任意规范扩展。
# https://swagger.io/specification/#specification-extensions
'EXTENSIONS_INFO': {},
# 附加到 schema 的根对象的任意规范扩展。
# https://swagger.io/specification/#specification-extensions
'EXTENSIONS_ROOT': {},
# 与 Oauth2 相关的设置。例如用于 django-oauth2-toolkit。
# https://spec.openapis.org/oas/v3.0.3#oauth-flows-object
'OAUTH2_FLOWS': [],
'OAUTH2_AUTHORIZATION_URL': None,
'OAUTH2_TOKEN_URL': None,
'OAUTH2_REFRESH_URL': None,
'OAUTH2_SCOPES': None,
}